I have some factory that's responsible to build Product
entity. To build the Product
it's necessary to retrieve all entities from a data source that should be associated with 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())
}
}
Personally I don't like the code above because of interface segregation principle violation at least. ProductFactory
can access to all methods of the repositories but should not supposed to do this.
I have a thought to create some kind of DAL called Storage
that could be used for specific business operation such as product creation. For example:
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)
}
Any suggestions?
The interface segregation principle that you want to apply is a good idea. It makes testing much easier, because you only need one mock and and not a whole bunch of mocks.
I would name the interface after the client that it is dedicated to, e.g. ProductFactoryRepository
.
The code that your ProductRepository
implements seems to be code that I usually would write in an interactor (aka. use case). Of cource you can extract it to an own class if you want.
But there is one thing that might break the architecture (if I understand your code). It is this function.
fun build(action: CreateProductDTO): Product {
As far as I understand you. The ProductFactory
is part of the entity (or domain) layer. The CreateProductDTO
seems to belong to the controller (web or transport) layer, because DTO usually stands for data transfer object.
But this would mean that you have a dependency from the entity layer to the transport layer, which breaks the architecture rules of the clean architecture.
The clean architecture proposes to pass plain data structures into an InputPort
. These data structures are often called RequestModel
and ResponseModel
.
The interactor should implement an input port such as this:
interface CreateProductInputPort {
fun createProduct(requestModel: CreateProductRequestModel, outputPort: CreateProductOutputPort)
}
The RequestModel
can either be a simple data like
data class CreateProductRequestModel(val shopId: Int, val tags: Array<String>, val categoryId: Int)
or you can declare an interface
interface CreateProductRequestModel {
fun getShopId(): Int
fun getTags(): Array<String>
fun getCategoryId(): Int
}
and let CreateProductDTO
implement it.
You should decouple the interactor (use case) from the transport layer in order to apply the single responsibility principle, because the DTO changes for other reasons than the use cases input model.
PS: I hope the Kotlin code I wrote is correct. I usually code in Java.