请解释存储库,映射和业务层关系和职责

时间:2016-08-24 16:37:18

标签: repository-pattern dto business-logic abstraction-layer

我一直在阅读很多关于这些东西,我目前正在开发一个更大的Web应用程序及其相应的后端。

但是,我开始使用一种设计,我要求Repository从数据库中获取数据并将其映射到DTO。为何选择DTO?仅仅因为到目前为止基本上所有东西都是简单的东西,不再需要复杂性。如果它有点复杂,那么我开始映射例如直接在服务层中的1对n关系。类似的东西:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // Entering Repository-Layer
    List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
    Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

    for(CarDTO car : cars) {
        List<WheelDTO> wheels = wheelMap.get(car.getId());
        car.setWheels(wheels);
    }

    return cars;
}

这当然有效,但事实证明,有时事情变得比这更复杂,我开始意识到如果我对此没有采取任何措施,代码可能看起来很难看。

当然,我可以在wheelMap中加载CarRepository,在那里进行轮映射,只返回完整的对象,但由于SQL查询有时看起来很复杂,我不想提取所有cars 他们的wheels加上getCars(Long ownerId)中的映射。

我显然错过了商业层,对吧?但我根本无法理解其最佳实践。

假设我有CarOwner个业务对象。我的代码看起来像这样:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // The new Business-Layer
    CarOwner carOwner = new CarOwner(carOwnerId);
    List<Car> cars = carOwner.getAllCars();

    return cars;
}

看起来很简单,但内心会发生什么?问题主要针对CarOwner#getAllCars()

我想这个函数会使用Mappers和Repositories来加载数据,尤其是关系映射部分需要处理:

List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

for(CarDTO car : cars) {
    List<WheelDTO> wheels = wheelMap.get(car.getId());
    car.setWheels(wheels);
}
但是怎么样? CarMapper提供的功能是getAllCarsWithWheels()还是getAllCarsWithoutWheels()?这也会将CarRepositoryWheelRepository移动到CarMapper,但这是存储库的正确位置吗?

如果有人能为我上面的代码展示一个很好的实际例子,我会很高兴。

其他信息

我没有使用ORM - 相反,我将使用jOOQ。它本质上只是一种类型安全的编写SQL的方法(使用它很有趣btw)。

以下是一个示例:

public List<CompanyDTO> getCompanies(Long adminId) {

    LOGGER.debug("Loading companies for user ..");

    Table<?> companyEmployee = this.ctx.select(COMPANY_EMPLOYEE.COMPANY_ID)
        .from(COMPANY_EMPLOYEE)
        .where(COMPANY_EMPLOYEE.ADMIN_ID.eq(adminId))
        .asTable("companyEmployee");

    List<CompanyDTO> fetchInto = this.ctx.select(COMPANY.ID, COMPANY.NAME)
        .from(COMPANY)
        .join(companyEmployee)
            .on(companyEmployee.field(COMPANY_EMPLOYEE.COMPANY_ID).eq(COMPANY.ID))
            .fetchInto(CompanyDTO.class);

    return fetchInto;
}

3 个答案:

答案 0 :(得分:1)

模式存储库属于数据访问对象的模式组,通常意味着对同类型对象的存储抽象。想想可以用来存储对象的Java集合 - 它有哪些方法?它是如何运作的?

根据这一定义,存储库无法与DTO一起使用 - 它存储域实体。如果您只有DTO,那么您需要更多通用DAO,或者可能需要CQRS模式。通常有一个单独的接口和Repository的实现,例如,它在Spring Data中完成(它自动生成实现,所以你只需要指定接口,可能是从公共接口继承基本的CRUD操作超级接口CrudRepository)。 例如:

class Car {
   private long ownerId;
   private List<Wheel> wheels;
}

@Repository 
interface CarRepository extends CrudRepository<Car,Long> {
   List<Car> findByOwnerId(long id);
}

当您的域模型是对象树并将它们存储在关系数据库中时,事情会变得复杂。根据这个问题的定义,您需要一个ORM。将关系内容加载到对象模型中的每段代码都是ORM,因此存储库将ORM作为实现。通常,JPA ORM会对场景后面的对象进行布线,更简单的解决方案(如基于JOOQ的自定义映射器或普通JDBC)必须手动完成。 没有银弹可以有效和正确地解决所有ORM问题:如果您选择编写自定义映射,那么保持存储库内的布线仍然更好,所以业务层(服务)将使用真实对象模型运行。 在您的示例中,CarRepository知道CarCar知道Wheel,因此CarRepository已经对Wheel具有传递依赖性。在CarRepository#findByOwnerId()方法中,您可以通过添加联接直接在同一查询中获取Car的Wheel,或将此任务委派给WheelRepository,然后仅进行连接。此方法的用户将收到完全初始化的对象树。例如:

class CarRepositoryImpl implements CarRepository {

  public List<Car> findByOwnerId(long id) {
     // pseudocode for some database query interface
     String sql = select(CARS).join(WHEELS); 
     final Map<Long, Car> carIndex = new HashMap<>();
     execute(sql, record -> { 
          long carId = record.get(CAR_ID);
          Car car = carIndex.putIfAbsent(carId, Car::new);
          ... // map the car if necessary
          Wheel wheel = ...; // map the wheel
          car.addWheel(wheel);
     }); 
     return carIndex.values().stream().collect(toList());
  }
}

业务层(有时也称为服务层)的作用是什么? 业务层对对象执行特定于业务的操作,如果要求这些操作是原子的,管理事务。基本上,它知道何时发出事务启动,事务提交和回滚的信号,但是没有关于这些消息在事务管理器实现中实际触发的内容的潜在知识。从业务层的角度来看,只有对象,边界和事务隔离的操作,而不是其他任何操作。它不必知道映射器接口后面的映射器或其他任何东西。

答案 1 :(得分:0)

在我看来,没有正确的答案。 这实际上取决于所选择的设计决定,这本身取决于您的筹码和您/团队的舒适度。

案例1:

我不同意你声明中以下突出显示的部分:

&#34;当然,我可以在CarRepository中加载wheelMap,在那里进行轮映射,只返回完整的对象,但是因为SQL查询有时看起来很复杂我不会#39 ; t想要获取所有汽车及其轮子加上getCars中的映射(Long ownerId)&#34;

上面的sql join会很简单。此外,它可能更快,因为数据库针对连接和获取数据进行了优化。 现在,我将此方法称为Case1,因为如果您决定使用sql连接从存储库中提取数据,则可以遵循此方法。而不是仅仅使用sql进行简单的CRUD,然后在java中操作对象。 (下同)

案例2:存储库仅用于获取&#34;每个&#34;域对象,对应于&#34; one&#34;表

在这种情况下,您正在做的事情已经是正确的。 如果您永远不会单独使用WheelDTO,则无需为其单独提供服务。您可以在汽车服务中准备好一切。 但是,如果您需要单独使用WheelDTO,请为每个人提供不同的服务。在这种情况下,服务层顶部可以有一个辅助层来执行对象创建。 我不建议通过制作存储库并为每个repo加载所有连接从头开始实现orm(直接使用hibernate或mybatis)

再次恕我直言,无论您采取何种方法,服务或业务层都只是补充它。因此,没有硬性规定,请根据您的要求灵活变通。如果你决定使用ORM,上面的一些将再次改变。

答案 2 :(得分:-1)

基本上我使用的方法是保留数据,数据并根据请求加载数据。

class ShinyCar {
   int key;
   int names;
   int wheeltype;
   List<Wheels> wheels; 
   public ShinyCar(CarDTO car) {
      this.fillFromDTO(car);
   }
   public int getKey() {
      return this.key;
   }
   protected void fillFromDTO(CarDTO car) {
      this.key = car.id
      this.names = car.names;
   }

   protected List<Wheels> getWheels() {
      if(this.wheels == null) {
          List<WheelDTO> wheels = this.ctx.select(...).from(wheels);
          if(wheels != null && wheels.size() > 0) {
              this.wheels = new ArrayList<Wheels>();
              for(WheelDTO wheel : wheels) {
                  wheels.add(new Wheel(wheel));
              }
              return this.wheels;
          }
          else {
              return new ArrayList<Wheel>();
          }
        else {
          return this.wheels;
        }
      }

   }
}