Варианты организации моего проекта с помощью: JAX-RS API, ServiceLocator и удаленных EJB

Я пытаюсь выяснить варианты, которые у меня есть для архитектуры моего проекта API.

Я хотел бы создать API с использованием JAX-RS версии 1.0. Этот 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 разных Версионных Сервисов могут совместно использовать код: UserV1Service а также OrderV2Service с помощью AnotherService,
  • Каждая версия имеет свой вид объекта (UserV1VO и не UserVO).

Что беспокоит меня по поводу кода выше:

  1. это ServiceLocator класс это не очень хороший подход для меня. Этот класс использует устаревший код из старой библиотеки, и у меня много вопросов о том, как этот класс работает. Способ использования ServiceLocator Это тоже очень странно для меня, и эта стратегия не подходит для проверки сервисов для моих модульных тестов. Я хотел бы создать новый ServiceLocator или использовать некоторую стратегию внедрения зависимостей (или другой лучший подход).
  2. UserService класс не предназначен для использования другим "внешним" сервисом, таким как OrderService, Это только для UserVxService, Но в будущем возможно OrderService хотел бы использовать некоторый код из UserService...
  3. Даже если я проигнорирую последнюю проблему, используя ServiceLocator Мне нужно будет сделать много lookups среди моего кода. Вероятность создания циклической зависимости (serviceOne lookup serviceTwo, что lookup serviceThree the lookup serviceOne) очень высока.
  4. В этом подходе ВО, как UserV1VO, может быть использован в моих неверсионных сервисах (com.organization.api.services), но этого не может быть. Хорошая архитектура не допускает того, что не разрешено. У меня есть идея создать новый проект, как api-services и положить com.organization.api.services там, чтобы избежать этого. Это хорошее решение?

Итак... идеи?

2 ответа

Решение

Несколько вещей, которые я вижу:

  • UserService в идеале должен быть основан на интерфейсе. Похоже, у них похожий контракт, но единственное отличие заключается в их источниках (RemoteEJB, LocalServiceLocator). Это должно быть возвращение DTO

  • UserV1Service extends UserService не следует использовать наследование, но вместо этого следует отдавать предпочтение композиции. Подумайте, что вам нужно сделать для v2 того же сервиса. Исходя из вашего примера, вы получите UserV2Service extends UserService, Это не идеально, особенно если вы используете абстрактные методы в базовом классе, специфичные для одной версии. Тогда внезапно другие сервисы с версиями должны обслужить это.

Для ServiceLocator

  • Вам лучше использовать инфраструктуру внедрения зависимостей, такую ​​как Spring или, возможно, CDI в вашем случае. Это относится только к вашему собственному коду, если ваш проект новый.

  • Для тех, кто трудно провести модульное тестирование, вы бы включили вызовы RemoteEJB в его собственный интерфейс, который облегчает макетирование. Тесты для RemoteEJB будут тогда интеграционными тестами для этого проекта.

Класс UserService не предназначен для использования другой "внешней" службой, такой как OrderService. Это только для UserVxService. Но в будущем, возможно, OrderService захочет использовать некоторый код из UserService

Нет ничего плохого в том, что Сервисы на одном и том же уровне общаются друг с другом.

При таком подходе виртуальные службы, такие как UserV1VO, могут использоваться в моих неверсионных службах (com.organization.api.services), но этого не может быть. Хорошая архитектура не допускает того, что не разрешено. У меня есть идея создать новый проект, такой как api-services, и поместить туда com.organization.api.services, чтобы избежать этого. Это хорошее решение?

То, что ты "мог" что-то сделать, не означает, что ты должен это делать Хотя может показаться хорошей идеей разделить слой на собственный проект; в действительности ничто не мешает разработчику воссоздать один и тот же класс в этом проекте или включить jar в путь к классам и использовать тот же класс. Я не говорю, что разделять это неправильно, просто нужно разделять по правильным причинам, а не "что, если сценарии".

Я в конечном итоге с этим решением (спасибо @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 я поговорил с командой и объяснил, как устроена организация. Команда хорошо это понимает, и никто не нарушает эту архитектуру.
Другие вопросы по тегам