建立具有关系的实体的正确方法是什么
What is the right way to build an entity with relationships
我有一些工厂负责构建 Product
实体。要构建 Product
,必须从应与 Product
.
关联的数据源中检索所有实体
class ProductFactory(
private val productRepository: ProductRepository,
private val shopRepository: ShopRepository,
private val categoryRepository: CategoryRepository,
private val tagRepository: TagRepository
) {
fun build(action: CreateProductDTO): Product {
val product = Product.Builder()
val shop = shopRepository.findById(action.shopId)
product.setShop(shop)
val tags = tagRepository.findAllById(action.tags)
product.setTags(tags)
val category = categoryRepository.findById(action.categoryId)
product.setTaxon(taxon)
return productRepository.save(builder.build())
}
}
我个人不喜欢上面的代码,至少因为违反了接口隔离原则。 ProductFactory
可以访问存储库的所有方法,但不应该这样做。
我想创建一种名为 Storage
的 DAL,它可用于特定的业务操作,例如产品创建。例如:
interface Storage {
fun findShopById(id: Long): Optional<Shop>
fun findCategoryById(id: Long): Optional<Category>
fun findAllTagsById(ids: Iterable<Long>): List<Tag>
fun save(product: Product)
}
有什么建议吗?
你在这里混淆了实例化和插入概念:
- 插入 是一种逻辑操作,当您将新实体添加到系统时发生,例如将新产品添加到产品目录。
- Instantiation 是一种技术操作,当您想要在内存中创建一个新对象时,例如当您将 DTO 转换为实体时,或者您想要检索现有对象时来自数据库的产品。
在DDD中,工厂是负责实体实例化的对象。实体 插入 是 存储库 的责任。大多数时候,您不需要编写工厂,因为您可以编写一些简单的代码,例如 Product product = new Product();
或使用一些映射库将数据持久性对象 (DPO) 投射到实体中。
但有时,对象实例化可能会成为一项复杂的任务,您可以编写一个工厂来分解此代码并从不同的 classes 中重用它。而不是写 Product product = new Product();
你会写类似 Product product = productFactory.MakeProduct(...);
的东西并注入工厂。
这意味着您的 ProductFactory
实际上应该是您的 Builder
class:
class ProductRepository(
private val productFactory: ProductFactory,
private val shopRepository: ShopRepository,
private val categoryRepository: CategoryRepository,
private val tagRepository: TagRepository
) {
fun insert(action: CreateProductDTO): Product {
val shop = shopRepository.findById(action.shopId)
val tags = tagRepository.findAllById(action.tags)
val category = categoryRepository.findById(action.categoryId)
val product = productFactory.MakeProduct(shop, tags, category)
product.Name = action.productName
return this.save(product)
}
}
你想应用的接口隔离原则是个好主意。它使测试更容易,因为您只需要一个模拟而不是一大堆模拟。
我会在它专用于的客户端之后命名接口,例如ProductFactoryRepository
.
您的 ProductRepository
实现的代码似乎是我通常在交互器(又名用例)中编写的代码。当然,如果需要,您可以将其提取到自己的 class。
但是有一件事可能会破坏架构(如果我理解你的代码)。就是这个功能。
fun build(action: CreateProductDTO): Product {
据我了解。 ProductFactory
是实体(或域)层的一部分。 CreateProductDTO
好像属于controller(web或transport)层,因为DTO通常代表data transfer object.
但这将意味着您具有从实体层到传输层的依赖性,这打破了干净架构的架构规则。
干净的架构建议将普通数据结构传递到 InputPort
。这些数据结构通常称为 RequestModel
和 ResponseModel
.
交互器应该实现这样的输入端口:
interface CreateProductInputPort {
fun createProduct(requestModel: CreateProductRequestModel, outputPort: CreateProductOutputPort)
}
RequestModel
可以是像
这样的简单数据
data class CreateProductRequestModel(val shopId: Int, val tags: Array<String>, val categoryId: Int)
或者你可以声明一个接口
interface CreateProductRequestModel {
fun getShopId(): Int
fun getTags(): Array<String>
fun getCategoryId(): Int
}
让CreateProductDTO
实施它。
您应该将交互器(用例)与传输层分离,以便应用单一责任原则,因为 DTO 因用例输入模型以外的其他原因而更改。
PS:希望我写的Kotlin代码是正确的。我通常在 Java.
中编码
遵循 DDD 教条,两个聚合应该使用 id 相互引用,而不是将整个 Shop/Tag/Category 设置在 Product 聚合中 :) 这就是为什么你觉得这段代码很奇怪,打破这个规则创造了必要完全使用存储库。
我想,使用整个 Shop
是由您用于持久性的技术驱动的。
您在工厂中感兴趣的是检查具有该 ID 的商店是否确实存在。
这应该通过向您的 ProductFactory
执行查询(传递服务)来处理,这样它将与 Shop
的细节分离并且只能访问 API由您的域公开(通过服务 query/commands)
我有一些工厂负责构建 Product
实体。要构建 Product
,必须从应与 Product
.
class ProductFactory(
private val productRepository: ProductRepository,
private val shopRepository: ShopRepository,
private val categoryRepository: CategoryRepository,
private val tagRepository: TagRepository
) {
fun build(action: CreateProductDTO): Product {
val product = Product.Builder()
val shop = shopRepository.findById(action.shopId)
product.setShop(shop)
val tags = tagRepository.findAllById(action.tags)
product.setTags(tags)
val category = categoryRepository.findById(action.categoryId)
product.setTaxon(taxon)
return productRepository.save(builder.build())
}
}
我个人不喜欢上面的代码,至少因为违反了接口隔离原则。 ProductFactory
可以访问存储库的所有方法,但不应该这样做。
我想创建一种名为 Storage
的 DAL,它可用于特定的业务操作,例如产品创建。例如:
interface Storage {
fun findShopById(id: Long): Optional<Shop>
fun findCategoryById(id: Long): Optional<Category>
fun findAllTagsById(ids: Iterable<Long>): List<Tag>
fun save(product: Product)
}
有什么建议吗?
你在这里混淆了实例化和插入概念:
- 插入 是一种逻辑操作,当您将新实体添加到系统时发生,例如将新产品添加到产品目录。
- Instantiation 是一种技术操作,当您想要在内存中创建一个新对象时,例如当您将 DTO 转换为实体时,或者您想要检索现有对象时来自数据库的产品。
在DDD中,工厂是负责实体实例化的对象。实体 插入 是 存储库 的责任。大多数时候,您不需要编写工厂,因为您可以编写一些简单的代码,例如 Product product = new Product();
或使用一些映射库将数据持久性对象 (DPO) 投射到实体中。
但有时,对象实例化可能会成为一项复杂的任务,您可以编写一个工厂来分解此代码并从不同的 classes 中重用它。而不是写 Product product = new Product();
你会写类似 Product product = productFactory.MakeProduct(...);
的东西并注入工厂。
这意味着您的 ProductFactory
实际上应该是您的 Builder
class:
class ProductRepository(
private val productFactory: ProductFactory,
private val shopRepository: ShopRepository,
private val categoryRepository: CategoryRepository,
private val tagRepository: TagRepository
) {
fun insert(action: CreateProductDTO): Product {
val shop = shopRepository.findById(action.shopId)
val tags = tagRepository.findAllById(action.tags)
val category = categoryRepository.findById(action.categoryId)
val product = productFactory.MakeProduct(shop, tags, category)
product.Name = action.productName
return this.save(product)
}
}
你想应用的接口隔离原则是个好主意。它使测试更容易,因为您只需要一个模拟而不是一大堆模拟。
我会在它专用于的客户端之后命名接口,例如ProductFactoryRepository
.
您的 ProductRepository
实现的代码似乎是我通常在交互器(又名用例)中编写的代码。当然,如果需要,您可以将其提取到自己的 class。
但是有一件事可能会破坏架构(如果我理解你的代码)。就是这个功能。
fun build(action: CreateProductDTO): Product {
据我了解。 ProductFactory
是实体(或域)层的一部分。 CreateProductDTO
好像属于controller(web或transport)层,因为DTO通常代表data transfer object.
但这将意味着您具有从实体层到传输层的依赖性,这打破了干净架构的架构规则。
干净的架构建议将普通数据结构传递到 InputPort
。这些数据结构通常称为 RequestModel
和 ResponseModel
.
交互器应该实现这样的输入端口:
interface CreateProductInputPort {
fun createProduct(requestModel: CreateProductRequestModel, outputPort: CreateProductOutputPort)
}
RequestModel
可以是像
data class CreateProductRequestModel(val shopId: Int, val tags: Array<String>, val categoryId: Int)
或者你可以声明一个接口
interface CreateProductRequestModel {
fun getShopId(): Int
fun getTags(): Array<String>
fun getCategoryId(): Int
}
让CreateProductDTO
实施它。
您应该将交互器(用例)与传输层分离,以便应用单一责任原则,因为 DTO 因用例输入模型以外的其他原因而更改。
PS:希望我写的Kotlin代码是正确的。我通常在 Java.
中编码遵循 DDD 教条,两个聚合应该使用 id 相互引用,而不是将整个 Shop/Tag/Category 设置在 Product 聚合中 :) 这就是为什么你觉得这段代码很奇怪,打破这个规则创造了必要完全使用存储库。
我想,使用整个 Shop
是由您用于持久性的技术驱动的。
您在工厂中感兴趣的是检查具有该 ID 的商店是否确实存在。
这应该通过向您的 ProductFactory
执行查询(传递服务)来处理,这样它将与 Shop
的细节分离并且只能访问 API由您的域公开(通过服务 query/commands)