A use case of implementing Domain-Driven Design (DDD) in Django

This video features Seb Corbin at DjangoCon Europe 2022 in Porto, Portugal.

A use case of implementing Domain-Driven Design (DDD) in Django
0:31:40
Published October 17, 2022
6,495 views

A use case of implementing Domain-Driven Design (DDD) in Django by SebCorbin

Our feedback on integrating some DDD tactical patterns in OSIS, a long-term open-source project for UCLouvain university.

Summary

Seb Corbin explains how Domain-Driven Design can be introduced into a large, long-lived Django system, using the OSIS student information platform and its admissions project as an example. DDD puts the business domain and a shared vocabulary first, separates the system into bounded contexts, and keeps domain logic in framework-independent Python while Django remains in the infrastructure and presentation layers. He demonstrates aggregates, value objects, validators, repositories, commands, use cases, dependency injection, and message buses, arguing that this structure improves testing, reuse, readability, and maintainability, while costing more development time and making some Django conveniences, queries, and transaction handling more difficult.

Key takeaways

  • DDD is most useful for complex, long-term systems with substantial business logic, not short projects, simple CRUD applications, or basic proofs of concept.
  • A ubiquitous language agreed with domain experts should appear consistently in discussions, documentation, and code, with bounded contexts separating terms and responsibilities.
  • The domain layer should be plain Python and independent of Django, while repositories provide swappable data access and infrastructure code maps Django models to domain aggregates.
  • Commands and queries are separated into use cases, which can be reused across views and tested with in-memory repositories and message buses.
  • This approach concentrates business complexity and improves testability and maintenance, but sacrifices some Django shortcuts, complicates cross-aggregate queries and transactions, and takes more time to implement.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction and OSIS Seb Corbin introduces himself, his team, and the large OSIS student information system project.
  2. 3:52 Domain-Driven Design An overview of DDD and its focus on modeling software around business needs and shared terminology.
  3. 6:11 From Data Models to Domains The talk contrasts Django’s data-driven approach with domain modeling for complex processes and changing teams.
  4. 7:48 When Not to Use DDD Criteria for avoiding DDD, including simple CRUD applications, short projects, and inexperienced teams.
  5. 9:23 DDD Patterns The speaker explains ubiquitous language, bounded contexts, and the importance of documenting domain terms.
  6. 12:30 DDD Architecture and Structure An example architecture separates the domain, repositories, services, presentation layer, and Django infrastructure.
  7. 16:22 DDD Vocabulary Definitions of use cases, aggregates, repositories, DTOs, domain services, and validators.
  8. 17:55 Use Cases and Event Storming The talk shows how event storming and event modeling help identify actions, objects, contexts, and application specifications.
  9. 19:26 Python Domain Implementation Recommendations for Python versions, typing, data classes, and keeping Django dependencies out of the domain layer.
  10. 23:18 Repositories and Django Mapping The speaker demonstrates repository interfaces, in-memory implementations, and mapping Django models to domain aggregates.
  11. 25:36 Commands, Message Buses, and Views A writing use case is connected to commands, dependency injection, message buses, and Django API views.
  12. 27:56 Benefits and Tradeoffs The talk concludes with DDD’s advantages for testing and maintenance, along with added complexity, transaction challenges, and cost.

Transcript

4,059 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:00

Hello everybody, my check, my check, okay. Did Django? So um I'll be talking about implementing German-driven design in Django Uh my name is Seb Corman, uh Sébastien Corman en français. Uh I've been doing Python and Django since twenty sixteen. Uh before that I was doing PHP and Drupal, but I came from the uh I came to the bright side. Um it's been uh ten years that I've been doing web development and um I'm a local meetup co-organizer and a tutor. I work for Machina Corpus, which is a small size agency in France. We do Python and PHP. We also

0:45

do frontend. and here are some um packages that you may know from us. We also have um products uh here are uh two products uh geotrake is a trail and natural park magic managing tool And the local kit is a low-code kit written in Node. js that you may have a look at. It's a bit like Airtable. Okay, um so I'll be talking about uh the OSIS project. Um the OSIS project is uh A project from UC Louvain, so University Catholic de Louvain-Laneuve, alias UCL. It means all this means Open Student Information System

1:31

Um they have been do uh working on it since 2016. And I think the dev team is over there. I don't know. But oh yeah. So if I say um bad things they will uh fire me. You can find the code on GitHub and uh it's almost open source. They have um unfortunately they've been closing the core uh recently for security reasons. But uh I'll be showing um OSIS admission uh subproject, so the code will be from that Okay, um about all this uh the business is quite large. They have um a student admission system which uh um uh treats uh lots of levels, student level.

2:18

They also manage the the student once it has been admitted. uh the courses, the catalogue of examinations, uh tutor applications, assessments and so on and so on. So as of our part, Machina Corpus has um worked on partnerships uh three years ago and now we are working on admission. So as you can see there is a lot of uh stuff to implement and maintain It's a large project and the architectures handles uh 35k students and 6k of staff members And it has to be security-oriented because of the data it handles. So for that, there are two servers and two Django that communicate together through an API.

3:06

The back office is only accessible through a VPN and it's an internal portal that the staff members connect on. And manager administrators. And for the front office side, it's accessible for students and tutors And it communicates through the API. So it's a large project. There are about two uh 250 models And yeah, that's it. So it's a big monolithic app, but uh they are continuously uh trying to make it better Um about the this architect architecture, um it's an historic decision. So we try to say that uh it could be replaced with a framework-based front end, but uh

3:52

Still no luck. Okay, so that is the beast that we are fighting fighting against. And now what about DDD? Who here knows uh have been heard have been hearing about DDD? Who uses it every day? Oh some people, okay. So DDD, also known as domain-driven design and not development, is an approach uh to um a project So it's a design approach, focus focusing on modeling the software to match domain, so to match business needs. And as a developer you will find you're not the expert. The business people are the experts in the domain

4:39

So it has been theorized by Eric Evans , early 20s, uh 2000. And you can find these books like uh the blue book um if you want to know more about it. So, business domain at the center. As I said, you're not the the experts, so you must speak the same language as the business experts and have the same terms. For that, you can uh create a glossary and it will be easier to speak uh throughout uh teams and people. Your code must reflect these terms and these terms they may be in French.

5:25

So if your domain is expressed in French You have to code in French. Well, it's a decision you you can make, but as for us, we decided to code in French. So I will be showing code that is in French And I will translate as much as I can, but um you know the important thing is uh the approach, the domain may be different for you You may be seeing both French and English and we like that we like to call it Franglish as a French who here uh speaks French Oh nice, lots of French people. Okay. So domain, wait. What? What what about data? We are handling data. Django is a model-driven or data-driven framework.

6:11

What would you um uh shift uh to uh a domain? So data works, but for complex processes the code like uh uh can look like spaghetti and once you have um Once you build up on data , it becomes a lot more complex. And when your model changes the structure of your data changes, you ri risk an alteration of the process of the business you are doing. So maybe we uh can find a uh more better thing. So also uh again DDD is a lot of about humans, uh we work together So humans change and teams change and when you code in uh to

6:58

in uh Django you have a way of thinking. So The business might change and another uh sorry, the teams might change and other uh people uh can come to the teams and don't um understand how you do you did what you did So it's more uh more about people than programming. And I hope most of you know this kind of image. It's uh traditional and it means like communication uh between teams, between the projects members, between the cloud the customer, the clients the business people uh sometimes it's a mess so from time to time uh you will find that the life cycle of a project is uh really messy

7:48

And to to avoid that we have to uh well we could use D D. Um Well, DDD is um is a thing that uh won't work in most projects, but for long-term projects like five years and more, um uh which have lots of business business domain uh it uh really fits your needs and you need a lot of money. So when not to use DDD And Django is a rapid a rapid application development framework, so if you have a simple CRUD, there's no need to use DDD. And if your business is already answered by one of the um uh lar uh the package that Django provides on third party, like CMSs and e-commerce, don't use DDD

8:38

Um if uh the maintenance is not needed over time like a proof of concept or uh the apt is is uh simple, don't use DDD no. Um if it's a short project like a few weeks. Don't don't bother. And the last um criteria is uh your dev team if your dev team is too junior it may be a bad choice too because as is a it's another approach yet that um people don't learn in schools or in other projects it may be hard to grasp And can I use DDD in my existing project? Well yes, um D D is made

9:23

like It is made to uh to refactor some uh parts of your domains. So for example the DDD has been introduced uh in OSIS five years after the first release, so it's a good example. Now some common patterns that you will find in a DDD approach We are often talking about ubiquitous language. Ubiquitous means everywhere, omnipresent, and That means like uh it's the most uh important thing about DDD. You need to take care of that because if people don't understand each other, it will be failing fast.

10:11

So you have to use the right terms across the domain and define them at the beginning before even even coding. Um they must come from the business uh people, uh from the business side, but they can be discussed because uh if there is a mista misunderstanding at first uh the developers uh will um uh you know uh ref uh re refrain from coding so um another thing to to note is that a term can have uh another meaning in another context And once stated, these terms must be written somewhere. So as for the code, a glossary could live in a dark string in a dunder init package. So

10:57

yeah, just write re write it somewhere. Okay, now another pattern is bounded contexts Bounded context means separate your concerns into small parts, each one solving a pro problem. So for example, an object can have different names in different contexts. A user can be a customer in the commerce domain, an account in an invoicing domain, and a recipient in the shipping domain. So multiple contexts, multiple terms, multiple um domains. In the code, a domain usually means a namespace. So new directory, new package, and so on. It does not mean that context cannot communicate

11:45

together, but data may have been to may have to be translated between them. And for as a as a um from the data approach you may store everything in a table and use it as you like. But in concept in a bounded context you may not need all data at uh all time. So even if it c if it's close in the storage, you may you might not need the payment mode when you are talking about commerce uh you know it's just used in uh during the invoicing uh during the payment and um yeah Another pattern is uh shared kernel.

12:30

Shared kernel is uh created when you have uh data that is um uh use uh frequently used and everywhere It can be present at multiple levels of the project, so a shared kernel can be at the top level or in a in a bounded context. Well it's to prevent duplication, but uh don't put everything in it because uh it can have a lot of business and that's not the goal. Now the architecture of the code. Um this is all this active activ architecture. Sorry. But some use different approach when structuring their code. So first you have the domain layer, which is your core.

13:17

business, the business domain. And then you have the repository layer that knows about the domain and manipulates data with it. And then you have the service layer that is aware of the repositories and the domain, but it it doesn't know about Django, for example. And then you have the UI, the graphical UI, the presentation layer, which is basically Django, your IPIs, your views, anything. So now Once we have these common patterns, we can think about a directory structure. So let's say this is my project or an app my project I will have a DDD

14:04

uh repository and uh this will be um by the basic Python. The the goal is to not use Django inside this directory. I'll tell you why later. And another uh top-level uh directory could be infrastructure, which is basically the implementation so Django implementation and in memory. So we have our context, let's say admission, which is the enrollment process process. uh formation continue which is continuing education and a doctorate and inside this context we can have subcontexts so Inside the admission context we can have preparation, validation

14:49

and all the level all the levels you need. Another example of a subcontext And here is a full example of a bounded context. So let's see what we have here. First, we have the main component of our model of our domain, which contains models uh services and validators. Then we have use cases which are which are split into read use case and write use case. Then we have the repository and some useful classes like the builder or DTOs which I will get back to later And we have a test uh directory

15:35

uh for unit testing So after that when you go into infrastructure you have you keep the same uh structure directory structure but um there will be implementation in them so you can add um a level for in-memory implementation for example and the rest is Django. Okay, so um hold up. Uh where does Django fits in that? Well you have two top-level directories, DDD and infrastructure, and Aside from that, you can have your views, your forms, your models, obviously. And uh well, that's it.

16:22

Let's talk about some vocal vocabulary that I've been using for now A first army is um a use case. A use case is some action that uh a user or uh an API would do And it splits into read, which is a query, and write, which uh would be a common. It's called CQS, common query separation. And it helps um uh focali uh focusing on um the thing that if it's a query it should only return data and not modify it. If it's a command It can load data and modify it. Then we have an aggregate. An aggregate is a root domain model entity.

17:07

It's basically the entry point for your business. uh to manipulate data and it's mask is often masking domain other domain entities which are um not so to be used outside of the contest the context A repository is your way to the data. So it's basically loading and saving aggregates or even fetching DTO. What is a DTO? A DTO is a data transfer object. It's basically a dumb class that doesn't do anything, has no business, no behaviors. And it's used to input and output data from contacts. Finally, uh we have a domain service

17:55

that is useful well when dealing with multiple or complex things like multiple aggregates or complex uh operations and validator which is used to uh manipulate uh to prevent manipulating your domain models uh in a wrong state Okay. So about use cases. They're also called application services. It's basically a user story and to To determine them you would need some um process. A special technique is called event storming. So Basically you get everyone in uh around the table. Uh I mean everyone developers, business, um

18:40

analysts. everyone and they talk about what the application would do to um to highlight what uh what are the the actions and objects and then you wrap all these things into bounded context. Then it's a good time to also establish the glossary. Then you could after that use event modeling, which is a technique for applying interface, a graphical interface onto what you just talked about earlier. And uh yeah, you you kind of have uh your spec specification after that.

19:26

Okay. So here is our return of uh experience on that, and some code. So a brief comment on Python. We recommend to use at least Python 3. 7 because there is there are data classes. And yeah, typing is uh is really nice too. Attitiers, I don't know if it's pronounced attitude. It's uh is a nice library for handling uh factor uh attribute factories for the non-slotted classes And using typing makes it really easier and you can execute my pi on the the on your code once

20:12

once you have been uh Once you have uh put uh typing on your classes. Um and as I said earlier, avoid using any of Django in DDD. Maybe you can uh use translations. for um uh you know um uh constants and maybe time zone but avoid uh um at most. Okay, so let's see an example of an aggregate. It's a root entity, so domain model, and it's It could be a single model in Django, but it could be multiple models, you know, linked with one-to-one fields or foreign keys or anything like that

20:59

It has an identity, usually the primary key, which may be an UID. It has a state, which are data attributes, well uh class attributes and some behaviors, methods that encapsulates the business. It may complex uh contain complex data Uh value objects are a kind of class that are not exposed outside of the context but can have behaviors too. And some behavior can ensure the state is valid before modifying it and it can be loaded through a repository. So let's see some code. So this is a épreuve confirmation, which is a confirmation exam.

21:45

And as we can see, there is the identity So it's another class which can be referenced into other classes, into the your repository and so on. Um it is referenced into the main class as an entity ID so we have épreuve confirmation identity and then you have data so I reference another um uh uh dom uh domain model here I store a date and uh um a file, a list of files. And I was talking earlier about value objects, uh demand prolongation, which means uh an extension request request uh is actually uh value objects.

22:31

So here I'm highlighting a behavior which is Do uh next uh ask for an extent extension request and it basically means okay create a demand prolongation object in my um aggregate Okay, so that was for the behavior and now let's see a validator. Um basically you you've put some of um you pass some um uh data par uh data attributes to your validator list and then if it validates it's okay you can do the next line but if it doesn't you raise an exception and you catch it on the upper level in uh Django.

23:18

Yeah, that's it. Now let's talk about a repository. A repository is based on a declared interface and is swappable So you can have an an in-memory version of this repository repository for unit tests and even a SQL alchemy if you don't uh love Django anymore. It could have three default methods, get, save, and delete, but it can have more like search, get by uh get by identifier, get by uh some data. The repository usually uh return aggregates or DTOs. So this is the interface. You have the get

24:04

method, the save and the delete, the search and the delete. And as for the get you can see that it returns an aggregate And um yeah for the save it just returns an identity. You will uh notice that it's only save, not create an update. We'll see that later You might say that we are reinventing the wheel, but uh it has some advantages, namely the unit test. And let's see a Django implementation of that. So in this implementation we are mapping the Django models that are created in our app.

24:50

to the aggregate and value object that that we were using earlier. Get means getting the and loading an object. I'm getting an object and then I'm instantiating the aggregate with all the data that it needs Then for the save part. The save part is as I said there is no update and there is no create, it's the same thing So we uh use update or create to um to save our aggregate, uh to save our model or aggregate as uh uh as needed. Okay, now a use case. Um let's see the

25:36

complete épreuve confirmation promoter command Which means uh okay I'm going to fill some data uh as a promoter of the of the of the examination request Um we have a command. A command is basically a data class with some data And the data is sent to a use case. With dependency injection you have the repository that comes along And then you load your aggregates uh with the repository You start by loading things

26:22

and then you um you do your business which is uh fill the data with it And then you save. That's a writing use case as simple as it is. After that, well you have to map your use case in a message bus. So The binding is done in a big class that references all the use cases and maps the right kind of repository onto the use cases. So you usually would have a message bus for Django and a message bus for in memory. And uh in memory uh message bus would be used in a unit

27:08

test. Now that we have the message bus, you can use it in uh view So it may be a command view like with a generic view or form or I don't know. Here is an example of an API view. So You have the first call to the message bus that loads the last confirmation paper identity And then there is a writing use case that completes that fill the data that were sent by the serializers. And since you are using um the uh commands that are data classes that are easily serializable, uh

27:56

you can um use uh um some kind of technique to convert a command to a serializer , be it an input or an output serializer And yeah, that's it. So now the pros of using DDD after some years Yeah the technical complexity is uh is concentrated concentrated in the DDD repository which is really nice. It's just a basic Python, no Django, easily testable, you need testable And um yeah you don't repeat yourself because a use case could be reused uh in another view in multiple places of the graphical interface. You have business validators that can can be reused

28:44

between business, between behaviors And uh yeah, you optimize query in a one file, the repository. And that still means that you can use Model Proxy and Manager So um it's very um uh focused and concentrated in one or or more file uh one or two files As of that, premature optimization is avoided because it's static uh it's a basic Python. And your code is well structured, easily readable. I would say some analysts would even be able to read it So yeah, that is the the nice thing. You are typing everything

29:30

because um basic Python So yeah, easier maintenance, um it's it has a learning curve but uh it's okay And you are basically decoupling the business from the framework. So you know if one day you uh you don't love Django anymore, you can switch on uh another framework. Now the downsides. Well you lose the sugar of Django, you know the rapid application developments, the auto-scaffolding from forms uh from models And you can still use it but you must rework it. And some queries, especially across multiple aggregates. become

30:15

really complex because you if you s if you are searching across aggregate aggregates you would need uh services that are really complex to code. The RM is um a bit trickier to optimize, but um yeah and it has no support for transactions so it could be added but it's tricky Because uh you're not into Django uh in uh Django anymore, so the repository can load values and then save them and then in another use case you have cons concurrency problems. As of uh of as of OSIS, most of the time um the students are uh modifying their own data and

31:01

On the other side, the manager are not modifying data every time, uh often. So it's it works for us. And it takes more time to code, so more time, more money Okay, uh that's it. If you think that I was talking really bullshit, you want the exact opposite, I suggest you go to the next conference after lunch by Adam Johnson uh which will be data oriented Django Django. Thank you.

Questions this talk answers

What is domain-driven design (DDD)?

DDD is a software design approach that models code around the business domain and its needs. It emphasizes using the business’s terminology and treating domain experts as the source of knowledge.

Discussed at 3:52

When should you not use DDD in a Django project?

DDD is unnecessary for simple CRUD apps, short-lived projects, proofs of concept, or projects already well served by existing Django packages. It can also be a poor fit when the development team is too inexperienced to adopt the approach effectively.

Discussed at 7:48

Can you introduce DDD into an existing Django project?

Yes. DDD can be applied incrementally to parts of an existing system; the OSIS project introduced it five years after its first release.

Discussed at 8:38

What is a bounded context in domain-driven design?

A bounded context separates concerns into smaller parts, each solving a particular problem, so the same concept can have different names or representations in different contexts. In code, it usually corresponds to a namespace or package, with translations used when contexts communicate.

Discussed at 10:57

How should a DDD project be structured in Django?

Separate the domain code from infrastructure: keep the domain in a basic-Python package containing models, services, validators, use cases, repositories, and tests, while Django-specific implementations live under infrastructure and the presentation layer. The goal is to avoid importing Django into the domain layer.

Discussed at 13:17

What are use cases in DDD, and how do reads and writes differ?

A use case, or application service, represents an action a user or API performs. Reads are queries that return data without modifying it, while writes are commands that can load and change data; this is the command-query separation pattern.

Discussed at 16:22

What is an aggregate in domain-driven design?

An aggregate is the root entry point for manipulating a part of the domain. It has an identity, state, and business behaviors, may contain value objects or other entities, and is typically loaded through a repository.

Discussed at 20:12

What is a repository in DDD, and how does it work with Django?

A repository is an interface for loading and saving aggregates or DTOs, commonly exposing methods such as get, save, delete, and search. Its implementation can map Django models to domain objects, while alternative implementations such as an in-memory repository support unit testing.

Discussed at 23:18

What are the advantages and disadvantages of using DDD with Django?

DDD concentrates technical complexity in testable, framework-independent Python, encourages reuse and clearer structure, and decouples business logic from Django. The trade-offs are more code and learning time, loss of Django’s scaffolding conveniences, harder cross-aggregate queries and ORM optimization, and more difficult transaction handling.

Discussed at 27:56

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos from DjangoCon Europe