15

My company is rewriting our web application from scratch. It's a large enterprise level application with a complex domain in the finance industry.

We are using an ORM (Entity framework) for persistence.

In essence, half of our application centers around gathering raw data from the user, storing it, and then the other half of the application that contains most of our actual domain logic takes that raw data to create our domain picture which differs greatly from those original raw inputs, and passes it into a calc engine, runs calcs, and spits out results, which are then displayed to the user.

In a DDD approach using layers, it seems like CRUD operations go through the domain layer. but at least in our case, this doesn't seem to make sense.

When a user goes to the edit screen to change an investment account for instance, the fields on the screen are the exact fields stored in the database, not the domain representation used later for calculations. So why would I load the domain representation of the investment account when the edit screen needs the database representation (raw inputs)?

After the user clicks "Done" on the investment account screen, and a POST is done to the controller, the controller now has pretty much an exact database representation of the investment account that it needs to save. But for some reason, I'm supposed to load the domain representation to make modifications instead of just mapping the controller's model directly to the database model (Entity framework model)?

So in essence I'm mapping a data model to the domain model, just so it can then be mapped back to the data model to persist. How does that make sense?

4 Answers 4

17

How does that make sense?

Short answer: it doesn't.

Longer answer: the heavyweight patterns for developing a domain model don't apply to those portions of your solution that are just a database.

Udi Dahan had an interesting observation that may help clarify this

Dahan considers that a service has to have both some sort of functionality and some data. If it does not have data, then it is just a function. If all that it does is performing CRUD operations on data, then it is database.

The point of the domain model, after all, is to ensure that all of the updates to the data maintain the current business invariant. Or, to put it another way, the domain model is responsible for ensuring that the database that acts as the system of record is correct.

When you are dealing with a CRUD system, you usually aren't the system of record for the data. The real world is the book of record, and your database is just a locally cached representation of the real world.

For instance, most information that appears in a user profile, like an email address, or a government issued identification number, has a source of truth that lives outside of your business -- it's somebody else's mail administrator that assigns and revokes email addresses, not your app. It's the government that assigns SSNs, not your app.

So you aren't normally going to be doing any domain validation on the data coming to you from the outside world; you might have checks in place to ensure that the data is well formed and properly sanitized; but its not your data - your domain model doesn't get a veto.

In a DDD approach using layers, it seems like CRUD operations go through the domain layer. but at least in our case, this doesn't seem to make sense.

That's right for the case where the database is the book of record.

Ouarzy put it this way.

Working on lots of legacy code though, I observe common mistakes to identify what is inside the domain, and what is outside.

An application can be considered CRUD only if there is no business logic around the data model. Even in this (rare) case, your data model is not your domain model. It just means that, as no business logics is involved, we don’t need any abstraction to manage it, and thus we have no domain model.

We use the domain model to manage the data that belongs inside the domain; the data from outside the domain is already managed somewhere else -- we're just caching a copy.

Greg Young uses warehouse systems as a primary illustration of solutions where the book of record is somewhere else (ie: the warehouse floor). The implementation he describes is a lot like yours -- one logical database to capture messages received from the warehouse, and then a separate logical database caching the conclusions drawn from the analysis of those messages.

So maybe we have two bounded contexts here? Each with a different model for an investment account

Maybe. I'd be reluctant to tag it as a bounded context, because it's not clear what other baggage comes along with it. It might be that you have two contexts, it might be one context with subtle differences in the ubiquitous language that you haven't picked up yet.

Possible litmus test: how many domain experts do you need; two domain experts to cover this spectrum, or just one who talks about the components in different ways? Basically, you might be able to guess how many bounded contexts you have by working Conway's law backwards.

If you consider bounded contexts to be aligned with services, it may be easier: should you be able to deploy these two pieces of functionality independently? Yes suggests two bounded contexts; but if they need to be kept synchronized, then maybe its just one.

6
  • Well there is validation and defaulting logic, but it only applies when creating/updating the raw inputs for an investment account. Then we use a much more rich model for the investment account when we use it as input to a calc engine. So maybe we have two bounded contexts here? Each with a different model for an investment account'
    – wired_in
    Aug 14, 2017 at 22:15
  • I just came back to this after several years, and your comment is resonating now more than before for some reason. There's a lot of good stuff here, but could you clarify one thing for me? You said "The point of the domain model, after all, is to ensure that all of the updates to the data maintain the current business invariant." This would apply to the part of our app that saves/updates information. The other part is just a calculation engine. It takes a representation of the data as inputs and spits out results. Is that not part of the domain model then? Since it doesn't affect stored data?
    – wired_in
    Mar 28, 2020 at 21:38
  • @wired_in Have you ever found the answer? Jul 9 at 8:10
  • Imho the calculation-engine also uses business invariants (constraints and rules defined by the business), hence are certainly part of the domain (avoid calculate, is a tech term; find something that express more the business purpose of the domain; maybe calculate is just a part of a bigger/whole business behaviour). Jul 12 at 12:34
  • About the CRUD operations I find myself modelling the domain also for that, because my AggregateRoot + Entities initialization provide generic validation and keep generically consistent initialization of data (then the mapping happens into Repositories). But beware: the mentioned "validation and defaulting logic" is 90% of the times only data-related or format-related or sanitation-related, and it is not something that belong to your domain (see @VoiceofUnreason 's examples above, about SSN and Emails). Jul 12 at 12:39
11

Ok imagine you implement your account creation page mapping the form post directly to an EF object which is then saved to the db.

Lets further assume that the database has various restrictions that prevent completely wrong data being inputted. Accounts always have Customers etc.

Everything seems to work ok. But then the business makes a new rule.

  • Accounts created on a Thursday get a 2% bonus interest rate. (assume interest rate is one of the account fields)

Now you have to put this logic somewhere and you don't have a domain object to put it in.

DDD assumes that you will always have these kind of rules, and you probably do. Creating an account must have various checks, an audit log etc its not going to just be 'write a row to the db'

Plan your domain out assuming there is no persistence or MVC controllers with extra logic in. Make sure you capture all of the requirements and they are all in the domain model.

16
  • 3
    That's a good way to put it. I hate finding business rules mixed with DB details. +1 Aug 12, 2017 at 6:24
  • Good points, but what if these validation rules only apply during creation and updating of the user's inputs? Then once we have the user inputs, the model that is created when running calculations is a completely different model. Should we have two domain models for an investment account? One for CRUD operations of raw inputs for the user and another for when those inputs are used to create the domain model used in calculations?
    – wired_in
    Aug 14, 2017 at 21:34
  • getting my questions confused. you'll have to give a full example. If you have domain logic it should go in a domain object. That doesnt mean you cant create another domain object later from the first one
    – Ewan
    Aug 14, 2017 at 21:42
  • Imagine a complex calculation engine. One of the inputs it needs to run calculations is an investment account, but all the investment account is to the calc engine is an income stream over some time period. This domain model of an investment account is completely different from the raw inputs that the user entered for this investment account. However, when the user is entering basic inputs like Name, Current Value, etc there still needs to be validation logic, but it should have nothing to do with the model the calc engine uses. So are there two domain models for an investment account here?
    – wired_in
    Aug 14, 2017 at 21:57
  • 1
    I obvs missed the last question (or got bored) yes you can obvs name all your Domain objects BoundedContextNamespace.Account But I would advise against it. It just makes it hard to talk about vs AccountProfile and InvestmentPortfolio or whatever. Push the business a bit when it comes to naming if you have too many or important clashes
    – Ewan
    Mar 9 at 11:30
3

In your domain you shouldn't have to know that the database even exists.

Your domain is about business rules. The stuff that needs to survive when the company that made your database goes out of business. That is, if you want your company to survive. It's really nice when those rules don't care that you've changed how you persist data.

Database details exist and need to be dealt with. They should live somewhere else. Put them across a boundary. Carefully control how you communicate across that boundary or it's not a boundary.

Uncle Bob has this to say about what to put your data in:

Typically the data that crosses the boundaries is simple data structures. You can use basic structs or simple Data Transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object.

The important thing is that isolated, simple, data structures are passed across the boundaries. We don’t want to cheat and pass Entities or Database rows. We don’t want the data structures to have any kind of dependency that violates The Dependency Rule.

[…] when we pass data across a boundary, it is always in the form that is most convenient for the inner circle.

Clean Architecture

He also explains how your outer layers should be plugins to your inner layers so inner layers don't even know outer layers exist.

Clean architecture cheat sheet

Follow something like that and you'll have a nice place to ignore the database where you can worry about input validation rules, rules that input must be persisted somehow, rules to run calculations, rules to send those results to whatever output. It's actually easier to read this kind of code.

It's either that or you decide that your domain is really just to manipulate the database. In that case your domain language is SQL. If so fine but don't expect your implementation of the business rules to survive a change in persistence. You'll end up needing to completely rewrite them.

6
  • We are using an ORM (Entity Framework), so our database is already abstracted away, but the data models (Entity framework classes) naturally are pretty much 1 to 1 with the database tables. The problem is that in some parts of our application the user is essentially just updating the data model (the screen is just a list of text boxes where each text box is a field in the database (data model).
    – wired_in
    Aug 12, 2017 at 4:45
  • So I don't see a reason to not just use representations of the raw data (data model) when doing CRUD operations. We have a complex domain representation used for calculations, which is what I view as our domain model, but I don't see why I would load up that picture in the CRUD part of our application.
    – wired_in
    Aug 12, 2017 at 4:45
  • Define what you mean by "use representations of the raw data". Data is input, data is validated according to domain rules, data is persisted somehow, data is calculated against, results are output to whatever. Am I missing something? Aug 12, 2017 at 4:56
  • I'm trying to say that the raw data we get from the user for an investment account is not how we represent that investment account in the main parts of our application, like when it's used for calcs. For instance, we may have a boolean input we save in the database called IsManagedAccount. The user supplies us with this via a radio button on the edit screen. So that representation from the database down to the screen is 1 to 1. When we build up our domain model later in the application, we might have a ManagedAccount class, thus no boolean property. The two structures are vastly different.
    – wired_in
    Aug 14, 2017 at 21:41
  • So when the user is just editing the raw inputs on an edit screen, why would I load the domain picture and then add a lot of complexity to somehow map strongly typed ManagedAccount class back to a flat representation that is just a single class with an IsManagedAccount property?
    – wired_in
    Aug 14, 2017 at 21:42
1

Applying DDD theory:

There are two Bounded Contexts in that domain:

  • The calculations of the investment account. The investment account mathematical model is one element maybe an Aggregate.
  • Core Finance. The client investment account is one of the Entities.

Each Bounded Context can have a different architectural design.

Example:

The Client Investment Account is an Entity (maybe an Aggregate, depends on the domain) and the persistence of data is made via the Entity's Repository (RDB or other type of DB like an OO Database).

There is not a DDD approach to CRUD operations. To have a DB field tied to an object's data breaks design principles.

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