用于组织我的项目的选项:JAX-RS API、ServiceLocator 和 Remote EJB

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

我正在尝试找出我的 API 项目架构的选项。

我想使用 JAX-RS 1.0 版创建一个 API。此 API 使用来自更大、更旧且更复杂的应用程序的远程 EJB (EJB 3.0)。我正在使用 Java 6.

到目前为止,我可以做到这一点并且可以工作。但我对解决方案不满意。查看我的包裹配置。我的顾虑在代码后描述:

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

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;
  }

}

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);
  }

}

com.organization.api.services.UserService的例子:

public class UserService {

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

}

我的项目的一些要求:

上面的代码有什么问题:

  1. ServiceLocator class 这对我来说不是一个好方法。 class 使用旧库中的遗留代码,我对 class 的工作原理有很多疑问。 ServiceLocator的使用方法对我来说也很奇怪,这个策略是not good to mock the services for my unit tests。我想创建一个新的 ServiceLocator 或使用一些依赖注入策略(或其他更好的方法)。
  2. UserService class 不打算供其他 "external" 服务使用,例如 OrderService。仅适用于 UserVxService。但在未来,也许 OrderService 想使用来自 UserService...
  3. 的一些代码
  4. 即使我忽略最后一个问题,使用 ServiceLocator 我将需要在我的代码中做很多 lookups。创建循环依赖(serviceOne 查找 serviceTwo 查找 serviceThree 查找 serviceOne)的机会非常高。
  5. 在这种方法中,像 UserV1VO 这样的 VO 可以在我的未版本化服务 (com.organization.api.services) 中使用,但这不可能发生。好的架构不允许出现不允许的情况。我想创建一个新项目,例如 api-services 并将 com.organization.api.services 放在那里以避免这种情况。这是一个好的解决方案吗?

所以...想法?

我看到的几件事:

  • UserService 理想情况下应该基于接口。它们似乎有类似的契约,但唯一的区别是它们的来源(RemoteEJB、LocalServiceLocator)。这些应该返回 DTO

  • UserV1Service extends UserService 应该 不使用继承,而应该使用组合 。想想您需要为同一服务的 v2 做什么。根据您的示例,您会得到 UserV2Service extends UserService。这并不理想,特别是如果您最终在特定于一个版本的基础 class 中使用抽象方法。然后突然间其他版本化的服务需要满足这个需求。

对于 ServiceLocator

  • 你最好使用像 Spring 这样的依赖注入框架,或者在你的情况下使用 CDI。如果您的项目是新项目,这将仅适用于您自己的代码。

  • 对于难以进行单元测试的那些,您可以将 RemoteEJB 调用包装到它自己的接口中,这样可以更轻松地进行模拟。 RemoteEJB 的测试将成为该项目的集成测试。

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

同层的Services互相通信是没有问题的。

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?

仅仅因为您 "could" 做某事并不意味着您应该做。虽然将层分离到它自己的项目中似乎是个好主意;实际上,没有什么能阻止开发人员在该项目中重新创建相同的 class 或将 jar 包含在 class 路径中并使用相同的 class。我并不是说拆分它是错误的,只是说应该出于正确的原因拆分它而不是 "what if scenarios"。

我最终得到了这个解决方案(感谢@Shiraaz.M):

  • 我删除了我的服务中的所有扩展,并删除 危险的 ServiceLocator class。这 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, 在我的 Jax-rs 版本中。但是我的服务非常简单且易于创建,所以我的解决方案很简单:

    @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;
      }
    
    }
    

还有我的 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);
      }

    }

有了这个策略,很容易组合使用其他服务。

  • 如果有需要的话,以后我会引入Guice来在其余的资源和服务中(至少在服务中)注入依赖,并在有依赖的服务中去掉默认构造函数,使用相同的构造函数在测试和生产中。
  • 关于项目4,我和团队谈过并解释了组织情况。团队很了解这一点,没有人破坏这个架构。