Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Options to organize my project with: JAX-RS API, ServiceLocator and Remote EJBs

I'm trying to figure out the options that I have for the architecture of my API project.

I would like to create an API using JAX-RS version 1.0. This API consumes Remote EJBs (EJB 3.0) from a bigger, old and complex application. I'm using Java 6.

So far, I can do this and works. But I'm not satisfied with the solution. See my packages disposition. My concerns are described after the code:

/api/
    /com.organization.api.v1.rs -> Rest Services with the JAX-RS annotations
    /com.organization.api.v1.services -> Service classes used by Rest Services. Basically, they only have the logic to transform the DTOs objects from Remote EJBs in JSON. This is separated by API version, because the JSON can be different in each version.
    /com.organization.api.v1.vo -> View Objects returned by the Rest Services. They will be transformed in JSON using Gson.
    /com.organization.api.services -> Service classes used by versioned Services. 
        Here we have the lookup for Remote EJBs and some API logic, like validations. This services can be used by any versioned of each Service.

Example of the com.organization.api.v1.rs.UserV1RS:

@Path("/v1/user/")
public class UserV1RS {

  @GET
  public UserV1VO getUsername() {
     UserV1VO userVO = ServiceLocator.get(UserV1Service.class).getUsername();
     return userVO;
  }

}

Example of the com.organization.api.v1.services.UserV1Service:

public class UserV1Service extends UserService {

  public UserV1VO getUsername() {
    UserDTO userDTO = getUserName(); // method from UserService
    return new UserV1VO(userDTO.getName);
  }

}

Example of the com.organization.api.services.UserService:

public class UserService {

  public UserDTO getUsername() {
    UserDTO userDTO = RemoteEJBLocator.lookup(UserRemote.JNDI_REMOTE_NAME).getUser();
    return userDTO;
  }

}

Some requirements of my project:

  • The API have versions: v1, v2, etc.
  • The different API versions of the same versioned Service can share code: UserV1Service and UserV2Service using UserService.
  • The different API versions of different versioned Services can share code: UserV1Service and OrderV2Service using AnotherService.
  • Each version have his own View Object (UserV1VO and not UserVO).

What botters me about the code above:

  1. This ServiceLocator class it not a good approach for me. This class use legacy code from an old library and I have a lot of questions about how this class works. The way to use the ServiceLocator is very strange for me too and this strategy is not good to mock the services for my unit tests. I would like to create a new ServiceLocator or use some dependency injection strategy (or another better approach).
  2. The UserService class is not intended to be used by another "external" service, like OrderService. It's only for the UserVxService. But in the future, maybe OrderService would like to use some code from UserService...
  3. Even if I ignore the last problem, using the ServiceLocator I will need to do a lot of lookups among my code. The chance of create a cyclic dependency (serviceOne lookup serviceTwo that lookup serviceThree that lookup serviceOne) is very high.
  4. In this approach, the VOs, like UserV1VO, could be used in my unversioned services (com.organization.api.services), but this cannot happen. A good architecture don't allow something that is not allowed. I have the idea to create a new project, like api-services and put the com.organization.api.services there to avoid this. Is this a good solution?

So... ideas?

like image 447
Dherik Avatar asked Sep 02 '16 01:09

Dherik


1 Answers

A couple of things that I see:

  • The UserService should ideally be based off an interface. They seem to have a similar contract, but the only difference are their sources (RemoteEJB, LocalServiceLocator). These should be returning DTOs

  • UserV1Service extends UserService should not use inheritance but should instead favour composition. Think about what you'd need to do for v2 of the same service. Based on your example, you'd get UserV2Service extends UserService. This is not ideal especially if you end up with abstract methods in your base class that is specific for one version. Then all of a sudden other versioned services need to cater for this.

For the ServiceLocator

  • You're better off using a dependency injection framework like Spring or perhaps CDI in your case. This would only apply to your own code if your project is new.

  • For the ones that are hard to unit test, you'd wrap the RemoteEJB calls into it's own interface which makes it easier to mock out. The tests for RemoteEJBs would then be integration tests for this project.

The UserService class is not intended to be used by another "external" service, like OrderService. It's only for the UserVxService. But in the future, maybe OrderService would like to use some code from UserService

There is nothing wrong with Services on the same layer to talk to each other.

In this approach, the VOs, like UserV1VO, could be used in my unversioned services (com.organization.api.services), but this cannot happen. A good architecture don't allow something that is not allowed. I have the idea to create a new project, like api-services and put the com.organization.api.services there to avoid this. Is this a good solution?

Just because you "could" do something doesn't mean that you should. While it might seem like a good idea to separate the layer into it's own project; in reality nothing stops a developer from either recreating the same class in that project or including the jar in the classpath and using the same class. I'm not saying that splitting it is wrong, just that it should be split for the right reasons instead of "what if scenarios".

like image 60
Shiraaz.M Avatar answered Oct 20 '22 00:10

Shiraaz.M