1

part 1:

If I have two entities

  1. User
  2. Post

Should the User entity only encompass methods that pertain to User Obj?

  • createUser, deleteUser, getUser, listUsers, updateUser, ...,updateSpeificOnUser?

What happens in the relationship if User can make Post. Do these additional method reside into User?

  • createPost, deletePost, updatePost

Or should it remain in Post entity - to be completely agnostic of which actor will call these actions?

Then in use-case layer, to make a conditional that checks the following


INPUT --> userId, postContent


  1. check getUser(userId) exists and their 'authorized titles' - otherwise throw error
  2. Evaluate whether credential is authorize to make this specific post. - otherwise throw error.
  3. calls post.create(userId) with user credentials. - otherwise throw error

OUTPUT --> return postId | 'error message'


part 2:

In the above use-case shows the business logic that checks conditions and entities for authentication and authorization to make that user action. Is this exactly where conditionals should reside? Because I can also picture moving conditions inside entity methods.

For instance post.create may take a second argument (userId, userTitle) the title may contain 'vip-user' which are allow to create post, as opposed to 'unpaid-user'. If I have this condition in the entity level, then in someways, I do not need step 2.

This begs the question, where should I put these conditions ? If I were to answer my own question, maybe theres no right answer but it depends on the requirement of the policy. Having conditions in the entity level means it's a critical business logic, and any use case will have to abide to such rules. On the other hand, relaxing the entity conditions, it allows for use-cases to adapt for various use-cases. And since use-cases are still application logic, enterprise logic are just more reusable logics tailored to its boundaries/constraints.

Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
cozycoder
  • 309
  • 3
  • 9

2 Answers2

1

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

  1. check getUser(userId) exists and their 'authorized titles' - otherwise throw > error
  2. Evaluate whether credential is authorize to make this specific post. - > otherwise throw error.
  3. 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

  1. Filtering, merging, etc. happens where the data is.
    • Usually faster
    • Doesn't scale very good. (Database is the bottleneck)
  2. Less network traffic, since the result only contains the data that is needed.
  3. 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

  1. Easy to understand.
  2. More network traffic
  3. 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.

René Link
  • 48,224
  • 13
  • 108
  • 140
  • thx, nice answer, may I ask what sort of detail would entity methods carry which use-case functions can make use of? I was caught up on Robert Martin's words “Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an object with methods, or it can be a set of data structures and functions.” Naturally, I imagined these methods for user - 'createUser', 'updateUser', post - 'create post' , 'delete post'. Butnow 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. – cozycoder Oct 06 '22 at 20:41
  • I'll need to read into repositories, because I've seen this show up a lot. Another thing I do wonder about however is - if our database implementation is just a detail that depends on business logic (entity and usecase). I'll assume my database functions has only fine grain queries to compose at use-case level. 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.thankyou – cozycoder Oct 06 '22 at 21:08
  • 1
    Hi @cozycoder, I updated my answer and hope that it helps you more now. – René Link Oct 07 '22 at 07:04
  • I've taken up much of your time. The picture is coming together clearly. And I love that my various tangents of uncertainties also led to your other answers on SO and your blog ! I thank you greatly. – cozycoder Oct 08 '22 at 04:42
1

Here we can apply Sinlge responsibility Principle of SOLID principles.

Should User entity only encompass methods that pertain to User Obj? createUser, deleteUser, getUser, listUsers, updateUser, ...updateSpeificOnUser?

You are right. Next time when you want to create set user name, it would be really logical to set name of user in user class, not in Post class. However, it depends what architecture is used in your application. Logic can be placed in model, services or in any other places

What happens in the relationship of User can make Posts. do these additional method reside into User? createPost, deletePost, updatePost

Yeah, all logic of Post should be placed in Posts. If you want to create Post in User, then you can use composition here:

public class User
{
    private Post _post;

    public User(Post post)
    {
        _post = post;
    }

    public void CreatePost()
    { 
       _post.Create();

    }
}

public class Post
{
    public void Create() { }
}

However, it depends what architecture is used in your application. Logic can be placed in model, services or in any other places

in the above use-case shows the business logic that checks conditions and entities for authentication and authorization to make that user action. Is this exactly where conditionals should reside?

If you are using traditional service layer architecture, then usually all business logic is placed in services. Read more where business logic should be placed in this beautiful answer

StepUp
  • 36,391
  • 15
  • 88
  • 148