8

我试图找出我的 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;
  }

}

我的项目的一些要求:

  • API 有版本:v1、v2 等。
  • 同一版本化服务的不同 API 版本可以共享代码:UserV1Service并且UserV2Service使用UserService.
  • 不同版本化服务的不同 API 版本可以共享代码:UserV1ServiceOrderV2Service使用AnotherService.
  • 每个版本都有自己的视图对象(UserV1VO而不是UserVO)。

上面的代码让我感到困扰的是:

  1. ServiceLocator门课对我来说不是一个好方法。这个类使用旧库中的遗留代码,我对这个类的工作方式有很多疑问。使用的方式ServiceLocator对我来说也很奇怪,而且这种策略不适合模拟 我的单元测试服务。我想创建一个新的 ServiceLocator 或使用一些依赖注入策略(或另一种更好的方法)。
  2. UserService不打算由另一个“外部”服务使用,例如OrderService. 它仅适用于UserVxService. 但在未来,也许OrderService想使用一些来自UserService...的代码
  3. 即使我忽略了最后一个问题,我也需要在我的代码ServiceLocator中做很多事情。lookups创建循环依赖(serviceOne查找serviceTwo那个查找serviceThree那个查找serviceOne)的几率非常高。
  4. 在这种方法中,VOUserV1VO可以在我的未版本化服务(com.organization.api.services)中使用,但这不会发生。一个好的架构不允许不允许的东西。我有创建一个新项目的想法,比如api-services并将其放在com.organization.api.services那里以避免这种情况。这是一个好的解决方案吗?

所以……想法?

4

2 回答 2

2

我看到的几件事:

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

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

对于服务定位器

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

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

UserService 类不打算由另一个“外部”服务使用,例如 OrderService。它仅适用于 UserVxService。但在未来,也许 OrderService 想使用一些来自 UserService 的代码

同一层的Services之间相互通信没有任何问题。

在这种方法中,可以在我的未版本化服务(com.organization.api.services)中使用像 UserV1VO 这样的 VO,但这不会发生。一个好的架构不允许不允许的东西。我有创建一个新项目的想法,例如 api-services 并将 com.organization.api.services 放在那里以避免这种情况。这是一个好的解决方案吗?

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

于 2016-09-07T22:18:26.853 回答
0

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

  • 我删除了我的服务中的所有扩展并删除了危险的 ServiceLocator 类。这种没有好的目的服务定位器的继承都是坏主意。我尝试使用 Guice 在我的 REST 资源中注入依赖项,但在我的 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 项,我与团队进行了交谈,并解释了组织如何。团队非常了解这一点,没有人破坏这种架构。
于 2016-10-18T12:15:25.233 回答