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

2

8

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?

Belkisbelknap answered 2/9, 2016 at 1:55 Comment(0)
H
2

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".

Huneycutt answered 7/9, 2016 at 22:18 Comment(3)
Hi! Thanks for the answer. Sadly, my ServiceLocator call static methods. I can't create an interface for this methods (#513377), I'm using java 6.Belkisbelknap
Done! I edited my question including this information, @HuneycuttBelkisbelknap
@Belkisbelknap After thinking about this again, I think that while you can't create an interface for the static methods, you should be able to create an interface with a class implementation that delegates to all the static methods ... so the intent is to wrap the static methods with something concreteHuneycutt
B
0

I end up with this solution (thanks @Shiraaz.M):

  • I remove all extends in my Services and delete the dangerous ServiceLocator class. This inheritances without a good purpose and service locator are both bad ideas. I try to use Guice to inject the dependencies in my REST resources, but it's not so easy to do that in my Jax-rs version. But my services are very simple and easy to create, so my solution was simple:

    @Path("/v1/user/")
    public class UserV1RS {
    
      private UserV1Service userV1Service;
    
      public UserV1RS() {
         this.userV1Service = new UserV1Service();
      }
    
      // only for tests!
      public UserV1RS(UserV1Service userV1Service) {
        this.userV1Service = userV1Service;
      }
    
      @GET
      public UserV1VO getUsername() {
        UserV1VO userVO = this.userV1Service.getUsername();
        return userVO;
      }
    
    }
    

And my UserV1Service:

    public class UserV1Service {

      private UserService userService;

      public UserV1Service() {
        this.userService = new UserService();
      }

      // for tests
      public UserV1Service(UserService userService) {
        this.userService = new UserService();
      }

      public UserV1VO getUsername() {
        UserDTO userDTO = userService.getUserName();
        return new UserV1VO(userDTO.getName);
      }

    }

With this strategy, is easy to user other services with composition.

  • If necessary, in the future, I will introduce Guice to inject the dependencies in the rest resources and services (at least, in the services) and remove the default constructor from the services that have dependencies and using the same constructor in the tests and production.
  • About the item 4, I talked with the team and explain how is the organization. The team understand well this and no one break this architecture.
Belkisbelknap answered 18/10, 2016 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.