2

I use laravel with repository pattern.

My project strucuture is:

I have an Entity class(POPO) which implements an Interface and has a repository class related to it.

For example for a User Entity, I have:

App\Entities\User - Entity class
App\Repos\User - Repository class
App\Interfaces\User - interface

Most of the actions in the system usually take place inside service class.

For example: for a payment process, I have a service called Pay. For a payment to complete these are the methods I may call

$pay = new \App\Services\Pay;
$pay->asUser($user); //a user object
$pay->forItem($product); // a product class
$pay->pay(); //actually paid

I am happy with this implementation and so far it is working great.

One thing I can see improvement on is the length of the Entity Class. For example, the User class has around 15 properties and all of them have a setter and a getter, (I do not want properties to be publicly accessible) which makes 30 methods and some actions and additional getters. Some examples would be, getInactiveUsers, getActiveUsers, registerAsAdmin etc. That is making these entities class huge. Some entities are crossing 1000 lines.

They are big to begin with because of getters and setters I would like to keep the logics or queries directly related to users in this class.

How can I keep these classes short and concise?

2 Answers 2

5

The first thing that you should do is to split your monolith into multiple modules, one module per Bounded context. This is also the most important architectural decision that you will make. If you do this correctly then all your models (Aggregates) will be as small as possible. In PHP, modules can be implemented as namespaces (top-level directories). So, an example would be:

App\Authentication
App\Authorization
App\Payment
App\Ordering

Then in each bounded context, you would have (i.e. for App\Ordering):

App\Ordering
    Domain
       Entities
          Buyer
          Order
          ...
       Repository
          BuyersRepository (interface)
          OrdersRepository (interface)
          ...
    Infrastructure
       Repository
          BuyersSqlRepository
          OrdersSqlRepository

To reduce code duplication, common classes could stay in a top-level namespace, i.e. Framework. Example are Query builders, GUID factories etc.

$pay = new \App\Services\Pay

In DDD services are always stateless. Your service seems to be stateful. Also, it is not clear how it can be used, what fields are optional and what are required for the pay method. You should make it like this:

$pay->pay($user, $product); //it is now clear that user and product are required

...the User class has around 15 properties and all of them have a setter and a getter, (I do not want properties to be publicly accessible)

From any relevant point of view, having public setters+getters and making the property as public is the same thing. Instead you should have methods that perform some specific action or return something, named with respect to the Ubiquitous language, respecting the CQS principle.

... getInactiveUsers, getActiveUsers ... That is making these entities class huge

I can't see an entity that would have such methods. These seem to be part of an Repository interface or a Domain service.

If your domain is complex, you should take a look at CQRS, which further helps in making the models more concise by splitting them in write/command models and read/query models.

7
  • One of the issues that I have with the approach is that I use Eloquent in the entity class. I would like to keep the related eloquent model(repository) within the related entities, For example, User entity with mostly relate to User model. With your suggestion I will have multiple repositories for the same model. That I fear will make the separation of repo and entity moot as the queries will be scattered and hard to manage. Any suggestions on that? Mar 11, 2018 at 6:48
  • 1
    I don't get it how would you have multiple repos for the same entity. If the entities are in multiple bounded contexts then they are not the same, by definition. Mar 11, 2018 at 7:11
  • So just to make sure that I understand this correctly, Does multiple bounded context mean that we will have multiple existence(definition) of a single Entity in two different contexts. User in context of Ordering module will have different definition and in context of Registration Module will have different definition i.e. two classes App\Ordering\User and App\Registraion\User will exist? Mar 11, 2018 at 7:33
  • @developernaren exactly. Users in different BCs have different attributes. Some attributes could be the same (i.e. Name) in both BCs. And most probable they will also have the same ID. So, every BC contains only the attributes that are used there and nothing more. Mar 11, 2018 at 7:37
  • One more question, If I am designing an api for example and I need to send two things at once, Order Details and Registration details, I would fetch the user for order differently and for registration differently. I would need two different queries to get the detail of the user (assuming that I need to fetch the user detail first to get other details). For optimization sake, I would like to make a single query share the details with two bounded context. How would I go about doing that? Mar 11, 2018 at 7:45
1

Okay, let's take a step back here and try to define the problem before we talk about the solution. It is really important to understand why you are wrestling with this problem as a means of informing you when future related problems begin to occur.

Keeping classes short and concise is one of the fundamental principals of DDD. This is because the goal of DDD is to model the behavior of a system in a way to meet the functional requirements of your business domain. Domain objects should exist to accomplish a task, not simply to hold data. I'd bet apples and oranges the only reason you have a User model is because there exists a [User] table in your data store. The issue here is that the data/attributes an object contains are rarely a good starting point when trying to model the functional requirements of a system. Database objects like User, aside from not implying any behavior, tend to be too abstract and encompass too much knowledge, so are therefore unsuitable for re-use in a particular domain.

A more suitable name might be Buyer or Shopper. While your domain Buyer may physically map to your [User] table, it seems incredibly unlikely that your payment process really requires 15+ attributes in order to carry out a sale (it may just need the id and PaymentMethod!). This model would only be responsible for managing a subset of the fields in your table. You may have another domain object named AccountManager that manages a [User]'s email and password. You see where I'm going here? The data store is an implementation detail that needn't concern your domain.

So the reason why you are encountering this issue has less to do with the details of a single object and more to do with a deficiency in your domain model.

I will agree with @Constantin Galbenu in that your project would benefit from organizing behavior into modules (directories) according to Bounded Context, but I will disagree with further classification into sub-directories named after DDD building blocks. It truly doesn't add much organizational value, prevents sub-namespaces entirely, and couples your physical directory structure to your logical model. The class names themselves should make it clear which "layer" the objects should fall into. Further nesting of namespaces can provide further partitions when it makes sense.

The last comment I'd like to make is in regard to your Pay service. While a service by that name may, indeed, need to exist in a system, when you are explaining the business process of purchasing a product to someone else, what do you say? If you say, "A User purchases a Product" I would expect some code that says that: $user->purchase($product). I'm fumbling with words trying to figure out how I can easily and concisely describe your approach or summarize your code. In DDD it's not enough for your code to just do the right thing, it's important that it says the right thing.

Even @Constantin Galbenu's example screams of an anemic model where the processes are separated into high-level functions applied on top of the model and your objects devolve into simple bags of (type-safe) data to be passed around as arguments. That's not DDD! That's is a functional approach. That doesn't make it wrong, just different. Objects should be paired with behavior, not separated from it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.