dis*_*ame 10 business-logic dto repository-pattern 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;
}
Run Code Online (Sandbox Code Playgroud)
这当然是有效的,但事实证明,有时事情变得比这更复杂,我开始意识到,如果我不对此做任何事情,代码可能看起来很丑陋.
当然,我可以加载wheelMap,在CarRepository那里进行轮映射,只返回完整的对象,但由于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;
}
Run Code Online (Sandbox Code Playgroud)
虽然看起来很简单,但内心会发生什么?问题的目标尤其在于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);
}
Run Code Online (Sandbox Code Playgroud)
但是怎么样?是CarMapper提供的功能getAllCarsWithWheels()和getAllCarsWithoutWheels()?这也会移动CarRepository和WheelRepository进入,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;
}
Run Code Online (Sandbox Code Playgroud)
模式存储库属于数据访问对象的模式组,通常意味着同一类型对象的存储抽象。想一想可用于存储对象的 Java 集合 - 它有哪些方法?它是如何运作的?
根据这个定义,存储库不能与 DTO 一起使用——它是域实体的存储。如果您只有 DTO,那么您需要更通用的 DAO,或者可能需要 CQRS 模式。Repository 具有单独的接口和实现是很常见的,例如在 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);
}
Run Code Online (Sandbox Code Playgroud)
当您的领域模型是对象树并将它们存储在关系数据库中时,事情会变得复杂。根据这个问题的定义,您需要一个 ORM。将关系内容加载到对象模型中的每一段代码都是一个 ORM,因此您的存储库将有一个 ORM 作为实现。通常,JPA ORM 在幕后进行对象的连接,更简单的解决方案(例如基于 JOOQ 或普通 JDBC 的自定义映射器)必须手动完成。没有什么灵丹妙药可以有效、正确地解决所有 ORM 问题:如果您选择编写自定义映射,最好将连接保留在存储库内,这样业务层(服务)将使用真正的对象模型进行操作。在你的例子中,CarRepository知道Cars。Car知道Wheels,因此CarRepository已经对Wheels 具有传递依赖。在CarRepository#findByOwnerId()方法中,您可以Wheel通过添加联接在同一查询中直接获取 Car 的 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());
}
}
Run Code Online (Sandbox Code Playgroud)
业务层(有时也称为服务层)的作用是什么?业务层对对象执行特定于业务的操作,如果这些操作要求是原子的,则管理事务。基本上,它知道何时发出事务开始、事务提交和回滚信号,但对这些消息在事务管理器实现中实际触发的内容一无所知。从业务层来看,只有对象的操作、事务的边界和隔离,没有其他的。它不必知道映射器或存储库接口后面的任何内容。