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



我一直在阅读关于这些东西的很多,我目前正在开发一个更大的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;
}

这当然有效,但事实证明,有时事情变得比这更复杂,我开始意识到,如果我对此不做任何处理,代码可能看起来相当难看。

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

我显然少了一个业务层,对吗?但我就是无法理解它的最佳实践。

假设我有一个Car和一个Owner业务对象。我的代码会看起来像这样吗:

// 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()

我想这个函数会使用映射器和存储库来加载数据,尤其是关系映射部分:

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的方式(顺便说一句,使用它非常有趣)。

下面是一个示例:

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

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

根据这个定义,Repository不能与dto一起工作——它是域实体的存储。如果您只有dto,那么您需要更通用的DAO,或者可能是CQRS模式。库的接口和实现通常是分开的,例如在Spring Data中就是这样做的(它自动生成实现,因此您只需指定接口,可能从公共超接口CrudRepository继承基本的CRUD操作)。例子:
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知道Car s。Car知道Wheel s,因此CarRepository已经对Wheel s具有传递依赖。在CarRepository#findByOwnerId()方法中,您可以通过添加连接直接在同一个查询中获取Car的Wheel s,或者将此任务委托给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());
  }
}

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

在我看来,没有正确答案。这实际上取决于所选择的设计决策,而设计决策本身又取决于您的堆栈和您的团队的舒适度。

案例1:

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

"当然,我可以在CarRepository中加载wheelMap,在那里做车轮映射,只返回完整的对象,但由于SQL查询有时看起来相当复杂我不想获取所有的汽车和他们的车轮加上在getCars(Long ownerId)"

上面的

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

Case2: Repository仅用于获取对应于"一个"表的"每个"域对象的数据。

在这种情况下,你所做的已经是正确的。如果您永远不会单独使用WheelDTO,那么就没有必要为它创建单独的服务。您可以在Car服务中准备一切。但是,如果您单独需要WheelDTO,则为每个服务创建不同的服务。在这种情况下,可以在服务层之上有一个帮助层来执行对象创建。我不建议通过创建存储库并为每个repo加载所有连接来从头实现表单(直接使用hibernate或mybatis)

我再说一遍,无论你从上面的方法中选择哪一种,服务层或业务层都只是对它的补充。所以,没有硬性规定,尽量根据你的需求灵活一些。如果您决定使用ORM,那么上面的一些内容将再次发生变化。

相关内容

  • 没有找到相关文章

最新更新