The author makes the distinction between what is in the "board game" or not with the following example:
eBay the website and eBay the board game both need buyers, sellers, items, and bids – but only eBay the website needs users, sessions, cookies, login/logout etc.
See how he mentions "buyers" and "sellers"? These are just different names for "users", but put in the context of the business.
The main point of the separation is that your business model (or as Uncle Bob puts it, "application agnostic business rules") do not need to know about the current user's authentication and authorization methods, or that it has a password, or uses Facebook or Google. Back to the eBay example, the business only needs to know that, in the context of the Order X, user Y is the buyer while user Z is the seller. This is what's important to make the business run - this is what the application does.
Always remember that the idea is to make the business rules completely agnostic of the delivery mechanism. You may have Users as part of your business model, there is nothing bad about it - Dropbox certainly needs, as you pointed out. But be sure to strip away anything related to how the user interacts with the system. This level of detail can be dealt with mostly by the delivery mechanism, and in some part by the application-specific business rules.
For instance, in some apps I've made, I check the user's authorizations within my use-cases, but the authentication methods are decoupled as plugins to be implemented from outside the applications. This has allowed me to easily add login options such as Faceebok, Twitter and Google, without affecting the structure of the applications.
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
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 needSQLTransactions
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
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:
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"
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
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.