Should User entity only encompass methods that pertain to User Obj?
createUser, deleteUser, getUser, listUsers, updateUser, ...updateSpeificOnUser?
Yes and No. If I only read the question I would say yes, but if I read the examples you provide I would say no. Because the examples you give seem to be use cases.
A use case uses a repository to query, persist and delete entities. You should not try to put those logic in the entities, because it's a violation of the single responsibility principle. If you place those logic in the entities, you are mixing persistence concerns with business rules.
What happens in the relationship of User can make Posts. do these additional method reside into User?
createPost, deletePost, updatePost
or should it be remain in Post entity - to be completely agnostic of which > actor will call these actions.
Once again I think the methods are use cases. I would create a CreatePostInteractor
that uses a CreatePostRepository
, pass the post data to the interactor so that it can do validation, create a post entity, persist it and then returns some kind of response. Maybe only the post id, but it can be much more.
Then in Use-Case layer, to make a conditional that checks the following
INPUT --> userId, postContent
- check getUser(userId) exists and their 'authorized titles' - otherwise throw > error
- Evaluate whether credential is authorize to make this specific post. - > otherwise throw error.
- calls post.create(userId) with user credentials. - otherwise throw error
OUTPUT --> return postId | 'error message'
A user is an actor who performs a use case. The authentication/authorization is made before calling a use case. A use case usually doesn't care about authorization.
If you want to make the authorization explizit by design, I would see the user as a kind of registry for use cases (technically spoken). E.g.
interface User {
<T> T getUseCase(Class<T> useCaseType);
}
Usually I would use this kind of User
in the controller layer.
I don't want to repeat myself here so if you want to read more about it you can read the blog I wrote some time ago about this considerations: Explicit access permission design with user roles
EDIT
....But now thinking, when the two entities relate, I suppose a method like userCreatePost wouldnt reside in either entity but represented as an application logic, an usecase.
I have to be more precise in my answer. An entity can have a method createPost
that is responsible for applying business rules while creating a post. This also means that a createPost
should instantiate all necessary entities and connect them. The only thing the method should not do is to persist the post. It only create the object structure. After that a repository is responsible for persisting the object structure in some way. Maybe it save the object structure just in a file, maybe it save it in a SQL or a NoSQL database. That is an detail.
The goal is that the createPost
, and thus the business rules in it, does not have any dependency to the persistence. Therefore it is easy to test.
I got a bit confused by your example listUsers
. If I would find this method on an entity I would ask myself "How can the entity list users when it doesn't have any dependency to the persistence? Are all users hard coded in the entity or are they somehow magically existent in memory?"
As such, use-case may fire 2, 3 or more of these basic queries to complete the use-case result. Does this clean architecture discourage bespoke queries then ? to keep maintainability, organize logic, we lose some speed, and possibility risk of race-conditions
These are questions that I often discuss with my colleagues to find a way to balance between maintainability and other issues like performance.
Let's list the pros and cons that help to find a solution.
Complex Queries
- Filtering, merging, etc. happens where the data is.
- Usually faster
- Doesn't scale very good. (Database is the bottleneck)
- Less network traffic, since the result only contains the data that is needed.
- Hard to test, because you need to start a database, setup some data and then you can run the test. Test that are hard to setup and take a long time are usually not often executed.
*Basic Queries
- Easy to understand.
- More network traffic
- Filtering, Merging, etc. happens at the application side.
- Can easier scale with the application nodes.
- Easy to test, because you don't need a database.
You can now go through the questions to decide which one fits better for your needs.
It also depends on the way you organize your data. We usually tend to put the data in a generic way into the database, so that it fits any use case. The advantage is that we do not have data duplications. The disadvantage is that we must create queries that slice and dice the data in a way we need it for a specific use case.
If you use a generic database format so that the data fits any use case, you often ran into the problem that the data structure fits better for some use cases then others. Since the data structure is made for multiple use cases you often have non-functional conflicts. E.g. you have a kind of reporting use case that is very slow, because the query to select, merge and so on. takes a long time. You might then assume that some indexes might help and voilà they help, but now the use cases that insert and update date get slower because they need to update the indexes you added. Maybe this is the reason why some developers prefer the CQRS.
If you go further you might not only want to separate read and write models, you maybe want to separate them by use cases. I would call this a UCRS - use case responsibility segregation.
Finally I think it's a trade-off that you and your team members have to make. The complexity is just there, you can move them from one to another place or try to distribute it, depending on the goal you want to achieve like maintainability, performance, testability, scalability and so on. Sometimes you find a good way to achieve multiple goals, but often if one goal is achieved another is not and vice versa. In my opinion this is the reason why we should make our software as flexiblie as possible, because goals might change and we have to adapt.