In my opinion, that's absolutely not how it's meant. And it's a violation of DRY.
The idea is that the entity / domain object in the middle is modeled to represent the domain as good and as convenient as possible. It is in the center of everything and everything can depend on it since the domain itself doesn't change most of the time.
If your database on the outside can store those objects directly, then mapping them to another format for the sake of separating layers is not just pointless but creating duplicates of the model and that is not the intention.
To begin with, the clean architecture was made with a different typical environment / scenario in mind. Business server applications with behemoth outer layers that need their own types of special objects. For example databases that produce SQLRow
objects and need SQLTransactions
in return to update items. If you were to use those in the center, you were to violate the dependency direction because your core would depend on the database.
With lightweight ORMs that load and store entity objects thats not the case. They do the mapping between their internal SQLRow
and your domain. Even if you need put an @Entitiy
annotation of the ORM into your domain object, I'd argue that this does not establish a "mention" of the outer layer. Because annotations are just metadata, no code that isn't specifically looking for them will see them. And more importantly, nothing needs to change if you remove them or replace them with a different database's annotation.
In contrast, if you do change your domain and you made all those mappers, you have to change a lot.
Amendment: Above is a little oversimplified and could even be wrong. Because there is a part in clean architecture that wants you to create a representation per layer. But that has to be seen in context of the application.
Namely the following here https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
The important thing is that isolated, simple, data structures are passed across the boundaries. We don’t want to cheat and pass Entities or Database rows. We don’t want the data structures to have any kind of dependency that violates The Dependency Rule.
Passing entities from the center towards the outer layers does not violate the dependency rule, yet they are mentioned. But this has a reason in the context of the envisioned application. Passing entities around would move the application logic towards the outside. Outer layers would need to know how to interpret the inner objects, they would effectively have to do what inner layers like the "use case" layer is supposed to do.
Besides that, it also decouples layers so that changes to the core don't necessarily require changes in outer layers (see SteveCallender's comment). In that context, it's easy to see how objects should represent specifically the purpose they are used for. Also that layers should talk to each other in terms of objects that are made specifically for the purpose of this communication. This can even mean that there are 3 representations, 1 in each layer, 1 for transport between layers.
And there is https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html which addresses above:
Other folks have worried that the net result of my advice would be lots of duplicated code, and lots of rote copying of data from one data structure to another across the layers of the system. Certainly I don’t want this either; and nothing I have suggested would inevitably lead to repetition of data structures and an inordinate of field copying.
That IMO implies that plain 1:1 copying of objects is a smell in the architecture because you're not actually using the proper layers and /or abstractions.
He later explains how he imagines all the "copying"
You separate the UI from the business rules by passing simple data structures between the two. You don’t let your controllers know anything about the business rules. Instead, the controllers unpack the HttpRequest object into a simple vanilla data structure, and then pass that data structure to an interactor object that implements the use case by invoking business objects. The interactor then gathers the response data into another vanilla data structure and passes it back to the UI. The views do not know about the business objects. They just look in that data structure and present the response.
In this application, there is a big difference between the representations. The data that flows isn't just the entities. And this warrants and demands different classes.
However, applied to a simple Android application like a photo viewer where the Photo
entity has about 0 business rules and the "use case" that deals with them is nearly non-existing and is actually more concerned about caching & downloading (that process should IMO be represented more explicitly), the point to make separate representations of a photo starts to vanish. I even get the feeling that the photo itself is the data transfer object while the real business-logic-core-layer is missing.
There is a difference between "separate the UI from the business rules by passing simple data structures between the two" and "when you want to display a photo rename it 3 times on the way".
Besides that, the point where I see those demo applications fail at representing the clean architecture is that they add huge emphasis on separating layers for the sake of separating layers but effectively hide what the application does. That is in contrast to what is said in https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html - namely that
the architecture of a software application scream about the use cases of the application
I don't see that emphasis on separating layers in the clean architecture. It's about dependency direction and focusing on representing the core of the application - entities and use cases - in ideally plain java without dependencies towards the outside. It's not so much about dependencies towards that core.
So if your application actually has a core that represents business rules and use cases, and / or different people work on different layers, please separate them in the intended way. If you're on the other hand just writing a simple app all by yourself don't overdo it. 2 layers with fluent bounds may be more than enough. And layers can be added later on as well.
Front end <--> API Service -> Service -> Repository -> DB
Right. This's, essentially, the design by layers proposed by Spring Framework. So you are in the "Spring's right way".
Despite Repositories, these are frequently used as DAOs, the truth is that Spring developers took the notion of Repository from Eric Evans' DDD. Repository interfaces will look often very similar to DAOs because of the CRUD methods and because many developers strive to make repositories' interfaces so generics that, in the end, they have no difference with the EntityManager (the true DAO here)1. Repositories tho add other abstractions like queries or criteria to enhance the data access.
Translated into Spring components, your design is similar to
@RestController > @Service > @Repository > EntityManager
The Repository is already an abstraction in between services and data stores. When we extend Spring Data JPA repository interfaces, we are implementing this design implicitly. When we do this, we are paying a tax: a tight coupling with Spring's components. Additionally, we break LoD and YAGNI by inheriting several methods we might not need or wish not to have. Moreover, we are (implicitly) assuming that the persistence data model is also the domain data model. This is quite the endemic of Spring, after many years working with Spring frameworks, you end up making everything data-centric.
That said, extending Spring Data JPA repositories is not mandatory. We implement a more plain and custom hierarchy of classes.
@Repository
public class DBRepository implements MyRepository{
private EntityManager em;
@Autowire
public MyRepository (EntityManager em){
this.em = em;
}
//Interface implentation
//...
}
Changing the data source now just takes a new implementation which replaces the EntityManager with a different data source.
//@RestController > @Service > @Repository > RestTemplate
@Repository
public class WebRepository implements MyRepository{
private RestTemplate rt;
@Autowire
public WebRepository (RestTemplate rt){
this.rt = rt;
}
//Interface implentation
//...
}
//@RestController > @Service > @Repository > File
@Repository
public class FileRepository implements MyRepository{
private File file;
public FileRepository (File file){
this.file = file;
}
//Interface implentation
//...
}
//@RestController > @Service > @Repository > SoapWSClient
@Repository
public class WSRepository implements MyRepository{
private MyWebServiceClient wsClient;
@Autowire
public WSRepository (MyWebServiceClient wsClient){
this.wsClient = wsClient;
}
//Interface implentation
//...
}
and so on.2
Back to the question, I don't think you need more layers. The layer you propose is going to end up as a proxy in between services and repositories or as a pseudo-service-repository where to place code you are not sure whether it belongs to the business or to the persistence.
1: Unlike many developers think, repository interfaces can be totally different from each other because each repository serves different domain needs. In Spring Data JPA, the role DAO is played by the EntityManager. It manages the sessions, the access to the DataSource, mappings, etc.
2: A similar solution is enhancing Spring's repository interfaces mixing them up with custom interfaces. For more info, look for BaseRepositoryFactoryBean and @NoRepositoryBean. However, I have found this approach cumbersome and confusing.
Best Answer
The important part of Clean Architecture (CA) are not the presenters and controllers. The important point of CA is that there is "core" of your application, which represents your business cases. And that all of UI, infrastructure, service or databases depend on this core and not vice-versa. I recommend wathing Uncle Bob's talk to get clearer picture.
When I look at your code, youre "core" use case is a pipeline. This pipeline consists of multiple parts, each run separately with database in between them. But together, they do some processing. What I would like to see is automated test that tests the whole pipeline as singular unit. The database and scheduler would be mocked out and the pipeline would not know if it is running in production or if it is being tested with in-memory database and run directly and not through scheduler.