In my country, you won’t make it through school without reading how Goethe’s Faust complains, I’ve studied now Philosophy – And Jurisprudence, Medicine, – And even, alas! Theology – All through and through with ardour keen! – Here now I stand, poor fool.
Sadly, none of his efforts and studies helped the doctor to perceive whatever holds the world together in its inmost folds.
And here we are in IT: We’ve studied languages and frameworks, libraries and even – alas – the IE! All through and through with ardour keen. But how many times did we focus on whatever holds the application together in its inmost folds? Today’s topic is the business domain.
Business Logic and Software Design
Business logic is sometimes considered to be unique, and it is by definition! If the business logic of an application wouldn’t be unique, there’d be no need to write an application, as there’s already an existing solution (with the exception of when an application exists but is not available). Hence, many developers see themself as pioneers, to boldly go where no man has gone before. Romantics aside, while the business logic itself may be unique to a noteworthy degree, the techniques to implement it are not. That’s why smart process models like Rational Unified Process or Scrum along with techniques like iterative and incremental development cycles were invited. Talented software architects have elaborated approaches for software design as well; among them Eric Evans who coined the term Domain Driven Design in his book with the same title.
Developers go boldly, where no man has gone before.
I’ll give an overview on how Domain Driven Design can influence the consulting process, as well as its basic concepts for designing a domain model. Finally, we will discuss the infrastructure requirements that are needed to implement a domain with ease.
Engineering Requirements
Let’s say you are a software architect on a non-trivial application with a non-trivial domain, like the core engine of a large logistic company. Many people are joining the planning talks, among them project managers, account managers, marketing, consultants and so on. Not everyone is needed to get the job done (I won’t share my opinions on to whom this applies), but two people will play a crucial role during the process of requirements engineering: you, the architect, and the domain expert.
A software architect (at least in business context) should have a very good abstract understanding on how processes work, how they are designed and optimized.
That’s true because business applications are mainly about designing efficient and beautiful digital equivalents of business processes. A domain expert should have an in-depth knowledge about a specific set of processes, namely the processes that are taking place in the logistic company and that should be reflected by the application. I found that business consultants, sales manager and marketing experts make a few good and valuable points along the way, but as long as you don’t have someone in the team who got his hands dirty in years of experience, the project will fail. For example, your domain expert should know the width of the loading ramp at the depot and if there’s enough space to install a barcode scanner.
So it’s you, specialized in digital business processes, software design and [fill in your favourite tools here], and an expert on logistics with knowledge on the company’s clients, employees and the day-to-day routine. Chances are, you’ll talk at cross-purposes. Domain Driven Design suggests some strategies that can form a powerful technical consulting service. Here’s mine:
- Create an ubiquitos language
- Build a glossary of keywords
- Shift from a process oriented view to a domain centered approach.
- Build a visual model as the foundation of your business logic.
Sounds like fun! Let’s dive into the details.
In every industry, every group of experts has its own terminology. It’s refined in every company and enriched with the companies special terms and product names. Think of IT: when people like us meet for serious geek talk, who else would understand a word? The same is true for your domain, and the first thing to do is to define a set of terms. Walk through the entire set of processes that the software should reflect and listen closely how the domain expert describes it. Any domain specific terms should be defined in a way that dictionaries do. You should be aware of words that sound familiar but are not in the given context. Some companies have never done that job before, even if it’s valuable for other areas.
Make a dedicated glossary of your ubiquitous terms, be sure that it gets approved by the client, and charge for the consulting process! A glossary may look like this:
Notice how a well defined glossary already sets dependencies and associations. Like the order, that hosts multiple items. You will surely have classes for those in your business logic! Your Order
class will presumably have a method like getItems()
. Without taking programming techniques into account, a glossary can set the ground work for your domain model! Along with that, you are building a language that is used throughout the entire project: in mails, in meetings, and definitely in code! Your code should reflect the domain; hence, it must be defined in the glossary. Here’s a rule of thumb: whenever you are creating a class that is not named after an entry in your glossary, your ubiquitous language may not be defined sufficiently yet!
Under the cover of darkness we have shifted the view on the requirements! Normally, a client describes what the to-be-written-software should do. A typical description might be, “We need a way to add notes to a client and print them out.” This is a good starting point, but it’s not focusing on the business domain. It introduces some kind of user interface, print functionality, and even more. You’ll surely need that in your application, but it’s not part of the domain. Domain Driven Design focuses on the modeling of the true purpose of an application: the business domain.
Everything else should arise from there, once the domain is done. The switch is the following: don’t implement processes, and build a pure domain that reflects the clients needs in objects. A way of visualizing the upper client description would be like this (getters and setters are only added, when needed for the understanding):
Now we have a set of classes and associations that do nothing but reflect the definitions from our glossary. Is this model capable of performing the needed tasks? Sure! You’ll need a PrinterService
and a user interface somewhere in your app, but they just need to grab some data from the domain. They are not necessary right now, and its implementation will not be decisive for the outcome.
The philosophy of DDD is based on the assumption, that a carefully designed domain layer can perform all needed processes with ease. A domain model is scalable, as it’s not built to satisfy a given task, it’s built to reflect a business concept. It’s interchangeable, as it’s not bound to any specific software—not even to a user interface. You can use the very same model in the barcode scanner on the loading ramp at the depot! As we’ll see in the next chapter, it’s not even bound to other components that are building your application.
The Domain Model
In one of my recent articles, I wrote about applying the KISS principle.
In one of my recent articles, I wrote about applying the KISS principle: most systems work best if they are kept simple rather than made complex. Well, when it comes to implementing a domain based on the philosophy of DDD, you can encounter a rather radical approach in the modern world of frameworks, patterns, and disciplines; such as, implement an ordinary object in just the plain language of your choice. No framework dependencies, no library conventions, no traces of any API, no fancy names. Just a plain old object (since a concept is not taken serious without a fancy name in the world of Java, they got one over there).
Entities vs. Value Objects
When we want to reflect the domain model, a crucial point is to define its state. In object oriented programming, the state of an object is defined by the state of its properties. Likewise, the state of the domain model is defined by the state of its objects. Hence, we must have a way to clearly define the state of objects. If we couldn’t do that, we would fail at easy use cases like, “How many orders are there?” because the answer always requires knowledge about the state of all Order
objects in a domain and a way to identify and distinguish them. DDD defines two types of objects: Entities and value objects.
An entity is a familiar concept, if you are familiar with relational databases.
Tables in a relational database usually have a unique identifier that distinguishes one row from another. The same is true for entities. An entity must have a clear identifier that is unique in the entire system. For an order, this could be a property of the type uint, named orderNumber
. Of course, you’d look into your glossary, where the correct term should be defined.
An entity stays the same when some properties change. For example you can add or remove items from an order, but it would be the same order. What happens, when you are changing the orderNumber
? Well, from the POV of your domain, one order is deleted while another one is created.
A value object is simple container for information. It is immutable once it is created. Changing one property means that you’d change the value object. A value object is defined by all of its properties; it does not need a unique identifier. The whole object is one. An example of a value object would be an OrderAddress
, as it’s defined by the name, address and city of the recipient. If you’d change one property, for example the city, the OrderAddress would change completely.
Dividing objects into value objects and entities is important to define the state of your domain – as this is the ground work to identify components. But it’s as well important to define them to have a scalable a maintainable domain. Entities are the representation of real world objects like Persons, Orders or Items. Value Objects are containers for information like colors or addresses and they are reusable and shareable among entities or even your entire system. Defining them may take some practice, as it’s depending on the use case whether you have a value object or an entity.
Associations, Aggregates, and Repositories
When we have a look back on the abstract of our glossary, we can see connections and dependencies between our objects in the domain layer. In DDD, this is called associations and it’s the model of interactions that are taking place.
For example, items are part of the order. If we’d process against a relational database, this would be a one-to-many relationship (or 1:n). If every order would have exactly one OrderAddress, it would be a one-to-one relationship. Since we do not care about relational databases and do only care about finishing the domain, the relationship can be easily expressed with two methods in the Order class: getItems()
and getOrderAddress()
. Note, that the first is plural (as there are many items) and the second is singular. If you’d have a many-to-many relationship, you would give both classes a getter method. Of course, you need setters, too—I’ve omitted them to keep the examples lightweight.
In DDD we try to avoid many-to-many relationships, as they tend to add complexity to the domain. Technically it means, that two objects have to be kept in sync during their lifecycle, and keeping things in sync can lead to violation of the DRY principle. That’s why the model refinement process should strive for simplicity. In many times, an association is stronger in one direction than the other, and it’s a good idea to redesign the structure to a one-to-many relationship. Check if the association is relevant for the business logic of your application. If it’s just occurring in non-core and rare use cases, you may want to look for another way to receive the needed information.
Associations build a tree of objects and you should end up with an association construct where every object can be retrieved through getter methods. This is a parent-child construct that ultimately leads to one root object. This is called aggregation in DDD. A good design ultimately leads to one aggregate that is capable of reflecting the entire domain. At the moment we have only looked at a small part of our glossary, but it seems that a client is our root aggregate:
Aggregates are an important part, as DDD tries to isolate the domain from the surrounding application. If we like to have information about a client, we ask for a root aggregate and can traverse through its children to access information through a clear interface of getters and setters.
DDD is like sales, it provides one face to the customer, the aggregate to the surrounding system. Hence, it gives access to a structured set of processes relevant information and methods; for example the order.
Domain Driven Design is like sales, it provides one face to the customer.
The surrounding application does access an aggregate through repositories, which are basically some kind of facade. In other words: A domain object is an aggregate if it has a repository. Repositories provide methods to query for aggregates. Examples may be findClientByEmail(string email)
or just findAll()
. They are as well performing updates and are adding new objects to the domain. Thus, they likely have methods like add(Client newClient)
or delete(Client toBeDeletedClient)
.
With an aggregate you are accessing children only through its parent. For example, a client aggregate gives you access to all orders by the client. But If you need to access the data from another perspective than the client’s, you can establish a second aggregate. Let’s say you want to have a list of all orders, no matter by which client they were placed. An order repository will get the job done!
Since the repository is the entry point for the surrounding application to the domain layer, it’s where other players enter the area. Remember that we are dealing with plain objects for now.
Have you asked yourself how this will become real? This is were infrastructure comes in. DDD is an excellent companion for frameworks, as it’s built on plain objects, with a simple scheme of value objects, entities and aggregates. However, since simplicity is power in IT, we are now in position to outsource the entire infrastructure part. Let’s have a look under the hood, and how DDD can be spread around an application.
Infrastructure and the Domain Layer
You may have noticed that our focus on the domain excluded a persistence layer as well as common things like views or controllers from our to-do list. The entire application may consist of much more complex stuff than just plain objects, and I want to point out a few steps that have to be done to wire the domain and the app together, as well as what implementation strategies exist. I will make some examples based upon FLOW3, an application framework with the main focus of providing DDD-infrastructure. It’s not necessary, but it won’t hurt if you read my introduction. In order to apply the business domain to an application, the following steps are common:
- Implementing a persistence layer that saves our domain objects – e.g. a MySQL database.
- Building a repository that abstracts the access to the relational database and provides an easy interface for queries.
- Build a factory service that generates the objects and builds the aggregate tree.
- Provide a service infrastructure to introduce non-domain relevant logic.
- Making the application domain-aware.
When you have a look on the comments on the article to Aspect Oriented Programming (AOP), you will see an interesting discussion about whether or not a framework should add its footprint via comment annotations. The approach in FLOW3 is based upon how Domain Driven Design is implemented. Have a look at this code:
/** * A Client * * @FLOW3\Scope("prototype") * @FLOW3\Entity */ class Client { /** * The clients name. * * @FLOW3\Validate(type="Text") * @FLOW3\Validate(type="StringLength", options={ "minimum"=1, "maximum"=80 }) * @ORM\Column(length=80) * @var string */ protected $name; /** * Get the Client's name * * @return string The Client's name */ public function getName() { return $this->Name; } /** * Sets this Client's name * * @param string $Name The Client's Name * @return void */ public function setName($name) { $this->name = $name; } }
This is a very simple class and it does not contain much business logic, but this will likely change once the application grows. FLOW3 is present through some code annotations. It’s defining the class as an entity and adds some validation rules to be applied (this is optional). Note that there is an annotation named @ORM\Column(length=80)
. This is an information for the persistence layer and we will come back to this in a moment.
FLOW3 uses annotations here to keep the domain clean. You are free to use the class anywhere else, as it’s still a plain object. You may opt to switch to the symfony framework, which uses the same persistence layer (Doctrine), hence the code would nearly work out of the box. By pushing the framework configuration outside of the scope of the PHP interpreter, the domain remains a plain old PHP object. You can reuse it even without any framework at all.
But now that the framework is aware of the object, it can now calculate the requirements for a MySQL database table. In order to store instances of the class client, FLOW3 (and Doctrine as persistence framework) would perform the following steps for you:
- Create a table with the name client.
- Add a column named name of the type string with the length of 80 chars
- Since we did not provide a unique identifier (which is required for entities), FLOW3 would kindly auto-generate one for us and add a table cell for it.
The property definition for the items in our order may look like this:
/** * The items. * * @ORM\OneToMany(mappedBy="order") * @ORM\OrderBy({"price" = "ASC"}) * @var \Doctrine\Common\Collections\Collection<\LogisticApp\Domain\Model\Item> */ protected $items;
Notice, that this returns a Doctrine Collection, which is some kind of wrapper for an array, like ArrayLists in Java. Essentially this means, that all elements have to be of the given type, in this case Item. I opted to add an order statement on how I want the collection to be organized (by the items prices).
The counterpart in the Item class could be:
/** * The order. * * @ORM\ManyToOne(inversedBy="items") * @var \LogisticApp\Domain\Model\Order */ protected $order;
It’s just the tip of an iceberg, but it should give you an idea on how things can be automated: Doctrine provides a powerful strategy on how to map associations to the tables where it stores the object. For example, since the items would translate to a one-to-many relation (one order may have many items) in the database, Doctrine would silently add a foreign key for the order to the item table. If you decide to add a repository for the item (making it an aggregate), you could magically access a findByOrder(Order order)
method. That’s the reason why we did not care about databases or persistence during domain creation—it’s something that a framework can take care of.
In case you are new to persistence frameworks, the way of mapping objects to a relational database is called ORM (Object-Relational-Mapping). It has some performance drawbacks, which are mainly caused by the different approaches that relational databases and the object model have. There are long discussions about it. However, in modern CRUD apps (not only domain driven), ORM is the way to go—mainly for maintenance and expandability reasons. However, you should know your ORM and have a good understanding of how it works. Don’t think you no longer need knowledge about databases anymore!
As you may have noticed, your objects can be quiet complex and have a long traversal line if they have many children, which in turn have many children of their own.
Hence, once the repository retrieves data from a database, they have to be transformed to objects in an intelligent way. Since we have now a persistence layer involved, transformation is much more complex than just instantiating an object. We have to manage the traversal line by reducing the relevant calls to the database to a minimum. Not all children are always needed, so they can be retrieved on demand.
Some objects will be value objects that need to be created only once, which can save lots of memory. That’s why, any domain layer needs a smart factory that generates the objects for you. Hence, in modern frameworks, the new
operator is considered to be way too low-level for modern applications. FLOW3 goes a long way to provide the opportunity to instantiate objects with the new
keyword, but the background compilation auto-changes plain object creation to powerful object management. Some features that your object manager / factory should be capable of, no matter which framework you use, are:
- Managing entities and value objects
- An intelligent way to provide children of objects on demand
- Injecting dependencies and services
You may have frowned on the last sentence. Throughout the whole article I have emphasized to use plain objects in the domain, and I even violated the “don’t repeat yourself” paradigm and mentioned it several times because it’s so important to DDD. And now I’m telling you that you have dependencies and services that need to be part of your domain…
There’s a sad truth in the real world: there isn’t such a thing like a pure domain. You’ll almost never encounter a client that starts from scratch; so, you have to satisfy circumstances like legacy systems. They may have a horrible implementation but the company can’t get rid of it. You may have to call services and APIs and retrieve data from various third parties, and these legacy systems influence the business domain.
Everything we’ve discussed so far are important, but the question on how a framework solves the dependency to non-domain services is critical to a clean Domain Driven Design. That is the reason why the FLOW3 team spent enormous efforts on the implementation of aspect-orientation; it’s a way to introduce services to the domain without touching the code without violating against the rule of plain old objects. There are other approaches, such as shifting the dependencies between services and the domain to the controller, but aspect oriented programming is by far the most elegant way I know. I’d like to hear your thoughts about this topic!
A good framework can give you a lot of support besides the points I mentioned. For example, FLOW3 transparently hands the domain objects in the view with its remarkably cool templating engine called Fluid. Writing Fluid templates, once the domain is done, is as relaxing as a day at the beach.
Summary
This article is just an introduction to Domain Driven Design. I have presented some of the core concepts, but I have to admit that it may be hard to grasp on theory alone. I want to encourage you to try Domain Driven Design for your own in a real world project. You’ll experience that the domain concepts are very intuitive to use.
I’ve been thrown into DDD like a fish out of water in a very large extbase project, without much prior knowledge of the concepts (extbase is a Domain Driven Design framework for building extensions for the CMS Typo3 and is based on FLOW3). It has broadened my outlook on how to think about software design, and I hope it will broaden yours, too.
No hay comentarios:
Publicar un comentario