Skip to content

Instantly share code, notes, and snippets.

@marcaube
Last active February 11, 2021 10:04
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcaube/7618402 to your computer and use it in GitHub Desktop.
Save marcaube/7618402 to your computer and use it in GitHub Desktop.
On Domain objects and code organization

A few days – or was it weeks – ago, I answered hastily to a question on the Symfony2 Google Group. The question is simple but the answer is not : How do your organise your code in a Symfony project?

I've had some time to think about it and wanted to elaborate on the subject. Writing makes me think, and thinking is good.

Everyone has their own way to do this. Some organize their code in a way that makes them faster – like in the RAD edition. Some others take the time and put the emphasis on making things isolated, modular and re-usable. Most of us fall somewhere in the middle, still trying to find the structure that suits us, our workflow or our organization the most.

My original answer to the question was to keep the domain logic separate from the bundle code, i.e. place it in a re-usable library. The reason is that your business logic should be encapsulated into models, outside of persistance concerns, logging and all that jive that comes with writing bundles.

In his book A year with Symfony, Matthias Noback dedicated a whole section, 5 chapters, on how to be a good Symfony developper. What resonated the most with me in these chapters was to rely less on the structure provided by the framework to write your business logic. The framework is the glue that holds everything together in a – mostly – elegant way. It's not the end-all be-all of your application.

If in a couple of years you decide that this framework doesn't fit your business case anymore, you take your marbles – or libraries – and you play somewhere else. It's as simple as that.

My own evolution

When I started working with Symfony, 2 years ago, I had a serious distaste for annotations and rapidly fell in love with the simplicity of yaml. I wrote schemas in yaml and generated the code for my entities using the Doctrine console commands.

Now I realize how fast the code gets mixed-up with that workflow. The persistance logic is defined in your yaml schemas and the behaviour is defined in the entities. It seems like everything is isolated right? Where do the persistance callbacks are written in this scenario? Ah yeah ... in the entities with your business logic, and that sucks.

If you were to move those entities outside of your Symfony project, let's say you wanted to write another app in another framework from the same business domain, you'd find yourself stuck with a lot of extra code you don't really need. Moreover, you'd have no clean way of extracting the business models from your bundle – copy paste anyone? If some business rule change, can you still make the change in only one place? Yeah, that's what I thought.

My answer to that, and that might change again in the future, is to write your business logic in a library. Define your models there and then in your bundles you define entities that extend those base models.

Let's write a bit of code to illustrate that ...

namespace Acme\DomainLib\Model;

class Customer
{
    private $name;
    private $lastname;
    private $balance;

    public function payBalance($money)
    {
        // business logic
    }
}
namespace Acme\AppBundle\Entity;

use Acme\DomainLib\Model\Customer as BaseCustomer;
use Doctrine\ORM\Mapping AS ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="customers")
 */
class Customer extends BaseCustomer
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $name;

    // ...
}

So, the code for the entities is pretty simple and all related to persistance, yay! Basically, we defined in our entity how our model should be stored and thats it. The business logic stays in the model, and the persistance logic stays in the entity.

We were also able to add an id attribute, which we don't really care about in our business domain, but do care about when dealing with databases and persistance. That's a nice separation of concerns right there.

Re-usability

Now it's easy to keep my business logic in a separate git repository and add it as a dependency to my project(s).

If the business rules change – and gosh you know they do –, we make the change in the library code and update our projects dependencies.

Now if I want to use Laravel for a sub-system, I can. If I want to use Silex for an internal API, I can.

@breda
Copy link

breda commented Apr 29, 2018

This is nice, thank you!

But this way of organizing has a dependency to Doctrine's Data-mapper implementation, what if someone wanted to work with Laravel, with its ActiveRecord implementation (Eloquent) ? This separation wouldn't work because you'd have to extend Eloquent models, which is, "the framework way" of doing things. How would you go about it ?

@Pictor13
Copy link

Eloquent implements ActiveRecord, not DataMapper; hence it's not suited for DDD and strong separation of concern (might be doable, but I don't personally know it since I have no experience with Laravel).
However the main point is to put the domain logic into a separate namespace.
You can maybe use composition and put the DomainModel implementation in a private property, inside a class that extends Eloquent and that works as proxy to rely on the Domain.

Random idea without, I repeat, any clue about Laravel or how Eloquent works 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment