Domain-driven design: a recipe for a pragmatist
Why are DDDs usually approached from the wrong side? And which side do you want? What do giraffes and platypuses have to do with all this?
Especially for Habr - a text transcript of the report "Domain-driven design: a recipe for a pragmatist." The report was made at the DotNext .NET conference, but it can be useful not only to donors, but to everyone who is interested in DDD (we believe you will master a couple of C # code examples). A video recording of the report is also attached.
Hello everyone, my name is Alexey Merson. I will tell you what Domain-Driven Design is and what is its essence, but first, let's figure out why it is needed at all.
Martin Fowler said: “There are few things that are less logical than business logic.” The giraffe is definitely one of these few. The distance between the brain and the larynx of a giraffe is only a few centimeters. However, the nerve that connects them reaches 4 meters. First, he goes down through the whole neck, there he goes around the artery and then he goes back almost the same way.
At first glance, there really is no logic. But this is just a dense legacy left over from ancient fish. In fish, as you know, there is no neck, so this nerve runs along the optimal path. And when mammals appeared after several million years of refactoring, the nerve had to be extended to maintain backward compatibility. Well, do not remodel because of some giraffe?
But the giraffe is okay, because there is a platypus.
Think it over. Mammal. With a beak. Lives mainly in water. Lays eggs. And besides, poisonous. It may seem that the only logical explanation for his existence is that he is from Australia.
But I think that everything is more banal. The contractor simply forgot about the design and saved up with StackOverflow, well, or what was there in those days.
I know what you’re thinking now: “Alexey, well, you promised Domain-Driven Design to us, and here’s some kind of“ In the animal world! ”
Colleagues, what is development? Development is when we take some part of the real world, a business process, and turn it into code, that is, build a software model. What problems await us along the way?
The first is the complexity of the business processes themselves, that is, the difficulty of understanding how the business works, what processes are taking place there, by what logic they are built.
The second problem is the implementation of these business processes in the form of code, the use of the right patterns, the right approaches, and so on. This is also a rather complicated topic.
Look, business processes are like that giraffe: they started with the simplest unicellular ones, and then “this” looks at you, and nobody understands either “where it came from” or “how it works”.
To build a successful model of such a process, you must first answer the question “why?”. Why do we want to build this model? What goals do we want to achieve? After all, if the customer wanted a stuffed giraffe, but got the guts, then he will be upset, even if the digestion in this model is implemented for the sake of eyes. And the customer will lose not only money and time, he will lose confidence in us as developers, and we will lose our reputation and customer.
But even if we figured out the goals, this still does not guarantee that we will not get the platypus as a result. The fact is that the goal is little to understand. The goal must be achieved. And this helps us Domain-Driven Design.
The main goal of Domain-Driven Design is to combat the complexity of business processes and their automation and implementation in code. “Domain” translates as “domain”, and development and design within the framework of this approach are pushed away from the domain.
Domain-Driven Design includes many things. This strategic design, and the interaction between people, and approaches to architecture, and tactical patterns - this is a whole arsenal that really works and really helps to make projects. There is only one “but”. Before you begin to deal with complexity with Domain-Driven Design, you need to learn how to deal with the complexity of Domain-Driven Design itself.
When a person begins to plunge into this topic, a huge amount of information falls out on him: thick books, a bunch of articles, patterns, examples. All this is confusing, and it is easy, as they say, not to notice behind the trees of the forest. I once felt this on myself, but today I want to share my experience with you and help you get through this jungle, finally starting to use Domain-Driven Design.
The term Domain-Driven Design itself was proposed by Eric Evans in 2003 in his unpronounceable book, which is simply called the Blue Book in the community. The problem is that the first half of the book Evans talks about tactical patterns (you all know them: these are factories, entities, repository, services), and people usually don’t get to the second half. The man looks: everything is familiar, I’ll go and get the DDD application.
On the right is what happens if you madly throw tactical patterns on the compiler. Left - if you use strategic patterns.
Since the release of the Blue Book, a rather strong DDD community has formed, many things have been rethought. Yes, and Evans himself admitted that he no longer understands how he could put an end to such an important thing as strategic design.
And 10 years later, in 2013, the Red Book was published by Vaughn Vernon. And in this book, the presentation is already built in the correct order: it begins with strategic design, with the basics. And when the reader has received the necessary base, then they already begin to talk about tactical patterns and implementation details.
Usually in reports on DDD they recommend reading Evans, on the Internet there are even whole manuals in which order you need to read chapters for proper immersion. I recommend doing it easier: start with the Red Book, read it, and only then move on to Blue.
And since strategic design is such an important thing, let's talk about its key ideas.
"Key ideas of strategic design"
In any business automation project, there are always domain experts. These are people who understand best how the business processes that are to be modeled work. These can be leading developers, executives, top managers. In general, it can be anyone, if only he understands those business processes with which we need to deal.
On the other hand, there are technical experts: developers, architects who are directly involved in the automation and implementation of applications. In the example depicted, the customer probably wanted a children's railway, but it turned out to be a kind of monster.
Why it happens? Because the interaction between technical experts and domain experts in a typical situation looks something like this: there is a big-big wall between them, and a manager walks along the top of this wall and first tries to hear what they are yelling from one side of the wall, then he tries to shout it to the best of the ligaments on the other side of the wall, and so on in a circle.
Sometimes a manager is deaf, then a whole chain of such managers can be built up, which, of course, does not contribute to the success of the project. And how should it be?
There must be constant interaction. Technical experts, domain experts - all project participants must constantly maintain communication, synchronize, discuss goals, ways to achieve them, and why do we do all this.
And here we come to the first and, probably, the most important key point of both strategic design and Domain-Driven Design in general.
Communication between the project participants forms what Domain-Driven Design calls ubiquitous language. He is not one in the sense that he is one for all occasions. Just the opposite. It is single in the sense that all participants communicate in it, all discussion takes place in terms of a single language, and all artifacts should be maximally in terms of a single language, that is, starting from TK and ending with a code.
Business scenarios
For further discussion, we need some kind of business scenario. Let's imagine this situation: The
director of the JUG.ru Group comes to us and says: “Guys, the flow of reports is growing, people, in general, are tired of doing everything manually ... Let's automate the process of preparing the conference.” We answer: “Okay!” - and get to work.
The first scenario that we will automate is: “The speaker submits an application for a report at a specific event and adds information about his report.” What do we see in this scenario? What is a speaker, there is an event, and there is a report, which means that it is already possible to build the first domain model.
Here we have a domain model: Speaker - speaker, Talk - report, Event - event. But the domain model cannot be limitless, cannot cover everything, otherwise it will become blurry and lose focus, so the domain model must be limited by something. This is the next key point.
Both the domain model and ubiquitous language are limited by the context that Domain-Driven Design calls bounded context. He restricts the domain model in such a way that all the concepts within it are unambiguous, and everyone understands what is at stake.
If they say “User”, then everything should be clear at once, it should have an understandable role, an understandable meaning, it should not be some kind of abstract user from the point of view of the IT industry.
In our case, this domain model is valid for the context of the preparation of the conference, so it is in the context that we will call the “Event planning context”. But for the speaker to add something, change information, he must somehow log in, he needs to be given some rights. And this will already be another context, “Identity context”, in which there will be some kind of their own entities: User, Role, Profile.
And look what the thing is here. When a person logs in to the system and intends to enter some kind of information, physically this is the same person, but in different contexts he is represented by different entities, and these entities are not directly related.
If we took and, for example, inherited Speaker from User, then we would mix things that cannot be mixed, and some attributes could be mixed by logic. And the model would lose focus on the specific meaning that it has, being divided into several contexts.
Demo: Sales service
Let's digress a little from dry theory and look at the code.
A conference is not only the preparation of content, but also sales. Let's imagine that a service for selling tickets has already been written, and a sales manager comes to us and says: “Guys! Once someone wrote this service, let's figure it out, something is not clear to me how the discount for regular customers is considered. ”
Having talked with the manager, we find out that the whole scenario of this service is this: by clicking on Checkout the final ticket price is considered taking into account the regular customer discount, and the order goes into the "Waiting for payment" state.
The code that we will now analyze can be viewed separately in the repository .
Open Solution, look at the structure:
It seems that everything looks good: there is Application and Core (apparently, people know about layers), Repository ... Apparently, the person mastered the first half of Evans.
Open OrderCheckoutService. What do we see there? Here is the code :
public void Checkout(long id)
{
var ord = _ordersRepository.GetOrder(id);
var orders = _ordersRepository.GetOrders()
.Count(o => o.CustomerId == ord.CustomerId
&& o.StateId == 3
&& o.OrderDate >= DateTime.UtcNow.AddYears(-3));
ord.Price *= (100 - (orders >= 5 ? 30m : orders >= 3 ? 20m : orders >= 1 ? 10m : 0)) / 100;
ord.StateId = 1;
_ordersRepository.SaveOrder(ord);
}
We look at the line with Price: here the price changes. We call our sales manager and say: “Here, in short, the discount is considered here, everything is clear”:
ord.Price *= (100 - (orders >= 5 ? 30m : orders >= 3 ? 20m : orders >= 1 ? 10m : 0)) / 100;
He looks over his shoulder: “Oh! So this is what Brainfuck looks like! And they kind of told me that the guys are writing in C # ”.
Obviously, the developer of this code responded well to an interview about algorithms and data structures. I wrote at school olympiads in about the same style. After some time, using formatting and
I think many have read Bob Martin's Clean Code. There he says about the Boy Scout rule: "The parking lot after we leave it should be cleaner than it was before we got there." Therefore, let's refactor this code so that it looks human and corresponds to what we talked about a little earlier about ubiquitous language and its use in the code.
Here is the refactored code.
public class DiscountCalculator
{
private readonly IOrdersRepository _ordersRepository;
public DiscountCalculator(IOrdersRepository ordersRepository)
{
_ordersRepository = ordersRepository;
}
public decimal CalculateDiscountBy(long customerId)
{
var completedOrdersCount = _ordersRepository.GetLast3YearsCompletedOrdersCountFor(customerId);
return DiscountBy(completedOrdersCount);
}
private decimal DiscountBy(int completedOrdersCount)
{
if (completedOrdersCount >= 5)
return 30;
if (completedOrdersCount >= 3)
return 20;
if (completedOrdersCount >= 1)
return 10;
return 0;
}
}
The first thing we do is transfer the discount calculation to a separate DiscountCalculator, in which the CalculateDiscountBy customerId method appears. Everything is read humanly, everything is clear: what, why and how. Inside this method, we see that we have globally two steps to calculate the discount. First: we get something from the order repository, everything’s according to the user case, you don’t even have to go inside if this is not the part that interests you now. The fact is that we get the number of some completed orders, after which we immediately consider the second discount for this quantity as the second step.
If we want to see how it is considered, we go to DiscountBy, and here almost the same thing is written in almost human English that our "type of brainfair" was before, everything is clear and precise.
The only question that could arise is in what units the discount is measured. It would be possible to add the word “percent” in the name of the method to make it clear, but from the context and the figures involved, most probably guess that these are percentages, and for brevity it can be omitted. If we want to see what the number of orders was there, then we will go to the Repository code and see. Now we will not do this. In our Service, we need to add a new DiscountCalculator dependency. And let's see what we ended up with in the second version of the Checkout method.
public void CheckoutV2(long orderId)
{
var order = _ordersRepository.GetOrder(orderId);
var discount = _discountCalculator.CalculateDiscountBy(order.CustomerId);
order.ApplyDiscount(discount);
order.State = OrderState.AwaitingPayment;
_ordersRepository.SaveOrder(order);
}
Look, the Checkout method receives orderId, then receives an orderId by orderId, according to the CustomerId of this order it considers the discount using the discount calculator, applies the discount to the order, sets the status to AwaitingPayment and saves the order. We had a script in Russian on the slide, but here we practically read the translation of this script into English and everything is clear, everything is obvious.
Do you see what the charm is? This code can be shown to anyone: not just programmers, but QA, analysts, customers. They will all understand what is happening, because everything is written in human language. I use this in our project, really QA can look at some pieces, check with Wiki and understand that there is some kind of bug. Because the Wiki says so, and the code is a little different, but he understands what is happening there, although he is not a developer. And in the same way, we can discuss the code with the analyst and discuss it in detail. I say, “Look, this is how it works in code.” Our last resort is not the Wiki, but the code. Everything works as it is written in the code. It is very important to use ubiquitous language when writing code.
This is the third key point.
There is so much confusion about Domain-Driven Design in things like Domain, Subdomain, Bounded context, how they relate to what they mean. It seems that everyone is limiting something, all are somehow tidy. But it is not clear then what the difference is, why they are so different invented.
Domain is a global thing, it is a global subject area in which this particular business makes money. For example, for DotNext this is a conference, for Pyaterochka it is a retail sale of goods.
Large corporations may have several domains. For example, Amazon is engaged in both the sale of goods via the Internet and the provision of cloud services, these are different subject areas.
Nevertheless, it is something global and cannot be automated directly, even to investigate it is difficult. For analysis, Domain is inevitably divided into Subdomains, that is, into subdomains.
Subdomains are parts of a business that, in our language, are highly connected, that is, they are some kind of isolated logical processes that interact with each other at some major level.
For example, if we take an online store, it will be the formation and processing of orders, it will be delivery, this is work with suppliers, this is marketing, this is accounting. Here are some of these pieces - this is what the business is divided into.
From the point of view of DDD, Subdomains are divided into three types. And here I want to say one more thing: often in books and articles Subdomain is simply reduced to Domain, but usually in the case when it is combined with the Subdomain type. That is, when they say “Core domain”, they mean Core Subdomain, please do not get confused in this. It blew my mind at first.
Subdomains are divided into three types.
The first and most important is Core. Core is the main Subdomain, this is the company's competitive advantage, what makes this company money, how it differs from its competitors, its know-how, whatever you call it. If we take the DotNext conference, then this is the content. You all came here for content, if there wasn’t such content here, you wouldn’t go or go to another conference. There would be no DotNext in the form in which it is.
The second type is Supporting Subdomain. This is also an important thing for making money, it is also something without which it is impossible, but it is not some kind of know-how, a real competitive advantage. This is what Core Subdomain supports. From the point of view of applying Domain-Driven Design, this means that less effort is spent on Supporting Subdomain, all the main forces are thrown on Core.
An example for the same DotNext is marketing. It is impossible without marketing, otherwise no one would have known about the conference, but without content marketing is not needed.
And finally, the Generic Subdomain. Generic is some typical business task, which, as a rule, can be automated with finished products or outsourced. This is what is also needed, but it does not necessarily require independent implementation by us, and even more than that, it will usually be a good idea to use a third-party product.
For example, selling tickets. DotNext sells tickets through TimePad. This Subdomain is perfectly automated by TimePad, and you don’t need to write a second TimePad yourself.
And finally, bounded context. Bounded context and Subdomain are always somewhere nearby, but there is a significant difference between them. It is very important.
There is a question on StackExchange how the bounded context differs from Subdomain. Subdomain is a piece of business, a piece of the real world, it is the concept of a problem statement space. Bounded context limits the domain model and ubiquitous language, that is, what is the result of modeling, and accordingly, bounded context is the concept of a solution space. In the process of project implementation, a kind of mapping of Subdomains takes place on bounded contexts.
A classic example: bookkeeping as a Subdomain, how the process is mapped, is automated, for example, 1C Bookkeeping, Elba or “My business” - is somehow automated by some product. This is the bounded context of accounting, in which there is its ubiquitous language, its own terminology. That’s the difference between them.
If we return to DotNext, then, as I said, tickets are mapped to TimePad, and content that is our Core Subdomain is mapped to a custom application that we are developing for content management.
Bounded context size
There is a moment that raises many questions. How to choose the right size for bounded context? In books, one can find such a definition: "Bounded context must be exactly such that the ubiquitous language is complete, consistent, unambiguous, unambiguous, consistent." Cool definition, in the style of a mathematician from a famous joke: very accurate, but useless.
Let's discuss how do we understand all the same: whether it should be a Solution, or Project, or namespace - which scale should be attached to the bounded context?
The first thing you can read almost everywhere: ideally, one Subdomain should map to one bounded context, that is, to automate with one bounded context. It sounds logical, because both there and there are limitations of a separate business process, in both cases some business terms, a single language appears. But here you need to understand that this is an ideal situation, you will not necessarily have this, and it is not necessary to try to achieve this.
Because, on the one hand, Subdomain can be quite large, and several applications or services can be obtained that will automate it, so it may turn out that several bounded contexts correspond to one Subdomain.
But there is a reverse situation, as a rule, this is typical for Legacy. That is, when they made a big, big application that automates everything in the world at this enterprise, then the opposite will turn out. One application is one bounded context, there the model will most likely be some kind of ambiguous, but Subdomains have not disappeared from this, respectively, one bounded context will correspond to several Subdomains.
When microservice architecture became fashionable, another recommendation appeared (although they do not contradict each other): one bounded context per microservice. Again, it sounds logical, people really do that. Because the microservice must take on some clear function, which internally has high connectivity, and communicates with other services through some kind of interaction. If you use a microservice architecture, you can take this recommendation for yourself.
But that is not all. Let me remind you once again that Domain-Driven Design is about a lot: about the language, about people. And you can’t ignore people and do only technical criteria in this matter. Therefore, I wrote this: one context is equal to X-man . I used to think that x is about 10, but we talked a bit with Igor Labutin ( twitter.com/ilabutin ) and the question remained open.
Here it is important to understand this: a single language remains unified while all participants speak, discuss and everyone understands it unambiguously. It is clear that an infinite number of people cannot speak the same language. Our history of mankind clearly shows this. In any case, some dialects appear, some of their meanings, now you can even add memes and so on. One way or another, the language will blur.
Therefore, it must be understood that the number of people who use this single language and, accordingly, take part in development, in automation, is limited. The books also talk about some political reasons: if two teams work under the leadership of different managers and work on the same bounded context, and for some reason these managers are not friends with each other, conflicts will begin and focus will be lost. Therefore, it will be much simpler and more correct to make two bounded contexts for each command and not try to combine what is not combined.
Architecture and Dependency Management
From the point of view of Domain-Driven Design, it doesn't really matter which architecture you choose. Domain-Driven Design is not about that; Domain-Driven Design is about language and communication.
But there is one important point, from the point of view of the criteria for choosing the architecture that interests us from the perspective of Domain-Driven Design: our goal is to maximally rid business logic of third-party dependencies . Because, as soon as third-party dependencies appear, terminology appears, words appear that do not enter into a single language and begin to litter our business logic.
Let's look at a classic example of architecture: the well-known three-layer architecture. As soon as they don’t call a domain layer (here the Business layer): business, core, and domain are all the same. In any case, this is the layer in which the business logic is located, and if it depends on the data layer, it means that some concepts from the data layer will somehow flow into the domain layer and will litter it.
Four-layer architecture is essentially the same, the domain layer still depends, and since it depends, third-party, unnecessary dependencies will make their way to it.
And in this sense, there is architecture that allows this to be avoided - it is onion-architecture (“onion”). Its difference is that it consists of concentric layers, the dependencies go from the outside to the center. That is, the outer layer can depend on any inner ones, the inner layer cannot depend on the outer ones.
The outermost layer is the user interface in a global sense (that is, it is not necessarily a human UI, it can be a REST API or anything). And the infrastructure, which often in general also looks like I / O, is the same database, in fact, a data layer. All these things are in the outer layer. That is, due to which the application somehow receives some data, commands, and so on, it is taken out, and the domain layer gets rid of the dependence on these things.
Next comes the Application layer - a rather holistic theme, but this is the layer in which scripts, user cases are located. This layer uses the domain layer to implement its concepts.
In the center is the domain layer. As we see, he no longer depends on anything; he becomes a thing in himself. And that is why the domain layer is often called “Core”, because it is the core, it is that which is in the center, that which does not depend on third-party things.
One of the options for implementing such an onion architecture is the hexagonal architecture, or “ports and adapters”. I brought this picture for intimidation, I will not talk about it. At the end of the post there is a link to one of a million articles about this architecture, you can read.
A little bit about tactical patterns: Separated Interface
As I said, firstly, most tactical patterns are familiar to everyone, and secondly, the whole point of my report is that they are not the essence. But I like the Separated Interface pattern separately, and I want to talk about it separately.
Let's go back to the code of our microservice and see what happened with the repository.
The domain layer had the IOrdersRepository.cs repository interface and its implementation, OrdersRepository.cs.
using System.Linq;
namespace DotNext.Sales.Core
{
public interface IOrdersRepository
{
Order GetOrder(long id);
void SaveOrder(Order order);
IQueryable GetOrders();
#region V2
int GetLast3YearsCompletedOrdersCountFor(long customerId);
#endregion
}
}
Here we have added here a certain method for receiving orders for the last three years GetLast3YearsCompletedOrdersCountFor.
And they implemented it in some form (in this case, through the Entity Framework, but it can be anything):
public int GetLast3YearsCompletedOrdersCountFor(long customerId)
{
var threeYearsAgo = DateTime.UtcNow.AddYears(-3);
return _dbContext.Orders
.Count(o => o.CustomerId == customerId
&& o.State == OrderState.Completed
&& o.OrderDate >= threeYearsAgo);
}
See what the problem is. The repository ended up in the domain layer, its implementation in the domain layer, but the code, starting with DateTime.UtcNow.AddYears (-3), does not inherently belong to the domain layer and is not a business logic. Yes, LINQ makes it more or less humanized, but if, for example, there were SQL queries here, everything would be completely sad.
The meaning of the Separated Interface pattern is that the service interface that we use in the domain logic is declared in the domain layer. We are talking about repositories and similar services in which the details of the implementation of these services are not business logic. The business logic is the fact of the existence of these services and the fact of their calling and use in the domain layer. Therefore, the repository interface remains in the domain layer, and the implementation moves to the infrastructure layer.
I prepared another option. The repository interface remains in the Core assembly, but the implementation moves to Infrastructure.EF.
Thus, we brought those concepts that were not peculiar to the domain layer to the infrastructure. As a side effect, we can replace this infrastructure with some other implementation. But this is not the main goal, the main goal is, as I said, to rid domain logic of third-party dependencies.
Once again about the language
Let's talk again, and again, and again about the language.
At the very beginning, we built the domain model “speaker - talk - event”. I think that no one has raised any special questions.
And here is the scenario on the basis of which we built this domain model:
Look, the scenario is in Russian, and the domain model is in English.
For non-English-speaking developers, this is something that you have to live with constantly.
Each of you, most likely, constantly does this process: translates from Russian into English and vice versa. Those who work with English-speaking customers and projects are a little easier, because the requirements are in English, discussions with customers in English, as a rule, all the scenarios are in English, the code is in English, and there is only communication within the team in Russian, which quickly grows in English (client - customer, order - order). And that cognitive load, that overhead, which is created by a constant translation, recedes a little.
But for those who work with Russian-speaking domains, especially those that are very difficult to translate into English, this translation becomes a problem. I came across this, and this is really a problem.
From this point of view, the logic of 1C company becomes clearer when they made their programming language in Russian. Because accounting, finance is a complex subject area in which there are a lot of terms, and to complicate it with an additional constant translation would be very tough.
Therefore, the 1C code looks like this. PascalCase adds fun, but in general it is something that anyone who understands all this
They got out, but are we the worse?
So I translated, this is the same use case that we discussed, only now it is in Russian. This is real code.that really compiles. It looks funny in combination with C # keywords, which, fortunately, cannot be translated. You can really show this code to someone who does not know English, and he will understand what is happening.
In summer, in St. Petersburg, we had a round table devoted to architecture, and there was talk about Domain-Driven Design. We discussed, among other things, a question related to the language, and there was a person who said that they in the company really write a C # domain layer in Russian. This allows them to lower the threshold of entry into the project for new developers and, in principle, reduce the overhead in understanding domain logic.
I was very interested in whether they run into problems somewhere on Continuous Integration. Because, although everything is already perfect everywhere with localization, Unicode and all that, somewhere some kind of problem will definitely come out. But they said that they only met something like that once, now I don’t remember where exactly. Let's just say that in 95% of cases everything worked fine for them, although they do not have manual assembly, but Continuous Integration, everything is configured, TeamCity is. It all works.
I do not urge from today to leave the report and write immediately in Russian, and even old projects to refactor and do everything in Russian. Not. But one must understand that there is a certain prejudice against the Russian language among non-1C programmers, and understand that this is a prejudice. And if in your case the benefit for the "patient" exceeds the harm, then technically you can do it. And even there are people who do this, and they succeed.
Let's summarize and get the very recipe for the pragmatist who is studying Domain-Driven Design.
The first is communication, communication and communication. Domain-Driven Design is about language, not technology. Technology is a solution to the problems that arise when you want to make your code human in terms of ubiquitous language. You are faced with technical problems, and they are solved by technical patterns, but not vice versa. That is, the repository is not for the sake of the repository, the repository is in order to take the implementation out of the domain layer, but in the domain layer everything would be readable.
Further. Everything has its limits, its limits. All models are limited by some contexts, as soon as you try to grasp the immensity, you will have a file, because the ambiguity of the terms will be lost, everyone will get confused, they will start calling one thing that way, the other, or vice versa, the name is one, but one understands this the other is this. And that will lead to problems.
A domain model should have a minimum of dependencies. All unnecessary addictions need to be burned with fire. But this is like a 100% test coverage. There is no need to show excessive fanaticism. This does not mean that it is necessary to ensure that not a single addiction exists. Because then you have to write your DSL language. You still have a dependency on the .NET framework, you still have some other things, so to achieve one hundred percent purity, most likely, it will not work, but this is normal. You need to strive to have a minimum of these dependencies.
Finally, business logic must be written in expressive language. It is written for a person, not a compiler, so you need to use ubiquitous language for business logic. And you will succeed.
Last but not least useful links
- Хабраблог Максима marshinov Аршинова. У него много статей, он очень хорошо пишет про DDD: например, «Как мы попробовали DDD, CQRS и Event Sourcing и какие выводы сделали». Я рекомендую его почитать, это очень будет полезно, у него очень много реальных историй.
- Блог Джимми Богарда, автора AutoMapper и фреймворка MediatR. И блог Александра Бындю, он пишет на русском и про Domain-Driven Design, как это работает в русскоязычных проектах.
- Дальше есть такой интересный сайт «F# for fun and profit», там про то, как использовать F# в Domain-Driven Design. Написано интересно, хотя местами, на мой взгляд, там шарп гнобят незаслуженно.
- And finally, the promised article about ports and adapters, about the hexagonal architecture.
That’s it, thanks, and here’s another reference to GitHub .
From the organizers of DotNext:
As Alexei noted, “if the conference hadn’t had such content, you would not have come here.” Now on the website of the next DotNext (to be held May 15-16 in St. Petersburg ), descriptions of the first reports have already appeared. The full program will be later, but from March 1 tickets will rise in price, so it’s more profitable to decide on a decision now.