6

I am striving to have the most maintainable architecture possible in my application, but I cannot decide on a proper domain model design.

Here is an example domain model for a user:

public class User // Referred to as the domain model
{
    // A model whose properties map exactly to a database table schema 
    private UserDataModel Model { get; set; } // Posts data back to the database

    public string FirstName
    {
        get
        {
            return Model.FirstName;
        }
        set
        {
            // Validation & Observer logic here
            Model.FirstName = value;
        }
    }
    public string LastName
    {
        get
        {
            return Model.LastName;
        }
        set
        {
            // Validation & Observer logic here
            Model.LastName = value;
        }
    }
    public string FullName
    {
        return $”{FirstName} {LastName}”;
    }
    public Company Company // Other domain model with a similar design
    {
        get
        {
            return Model.Company;
        }
        set
        {
            // Validation & Observer logic here
            Model.Company = value;
        }
    }

    // List of other domain models with similar designs
    public IList<Order> Orders { get; set; } 
    // …

    internal User(UserDataModel user)
    {
        Model = user;
    }

    // Domain specific methods here 
    //(e.g. IsAdmin, CalculateBill, SendPasswordResetEmail, CanPlaceOrder, etc.)
}

Essentially, my concerns relate to what are the best practices for having an effective, efficient, and maintainable domain design that allows for robust consumption, strong encapsulation, and high extensibility / maintainability or scalability. Specific questions are:

  1. Properties

    a) Should my domain model contain properties similar to the underlying data model or properties that are only relevant to the application’s consumption of the domain model (i.e. properties like FullName, boolean flags like Dirty (updates occurred), Prototype (i.e, brand new dummy model for creation by client/consumer))?

    b) Should the underlying data model be exposed publicly for access or should it only be accessible via methods? Should only parts of the data model be returned or the entire object?

    c) Should the domain model include linked objects (i.e like the Company object in the example) or just the relevant value to that linked object (i.e. the Name property of the Company object instead of the entire object)?

    d) Should setters be similar to above or only via methods called on the domain model or private methods called in the property setter (i.e. set { SetFirstName(value); } )? (Note: The setters will implement validation engine calls & observer calls & service calls, so they will be relatively chunky)

  2. Construction

    a) Should I approach construction of a new domain model from the client/consumer via several set methods or purely mapping the properties at the client/consumer level or via a factory / builder pattern?

    b) Should my domain model completely hide the underlying data structure it represents (i.e. appears as if it is a object that was never part of a database)?

    c) Should validation be done at construction level (i.e. as properties are set) or left up to the client/consumer calling an “IsValid” method on the domain model when appropriate?

  3. Validation

    a) Should validation errors be stored in a collection in the domain model or in a separate errors model / singleton class?

    b) Should validation be done at the domain model level or data model level?

  4. Retrieval

    a) For lists of objects (i.e. orders), should the list be private and access via a “GetOrder(string orderNo)” method? Wouldn’t this be a violation of the Single Responsibility principle (I.e. the method is a pseudo repository / factory) or no?

    b) How should retrieving an entire list be done without affecting the containing domain model or the domain models in the list? Should a clone be used despite it being potentially memory intensive (a user could have thousands of orders with their own linked domain models)?

I am ideally trying to avoid developing an unmaintainable domain design that is a pain to consume or test. I want the domain model to be as fluent & robust as possible thus allowing it to be read like prose to the client/consumer and representing the actual domain not the underlying data structures (i.e. it should be able to act as if it standing on its own without a backend).

7

Frankly, glue classes that merely pass through fields from the data model don't really add significant value to your application, nor are they particularly interesting from a functional perspective.

So before we get too deeply into your individual questions, let me propose a simple foundational architecture:

DB <---> ORM <---> SL/BLL <---> VM <---> V

The DB is your database. The ORM is your Object-Relational Mapper; it communicates with the database via SQL, and exposes CRUD methods. The SL/BLL is your Service Layer/Business Logic Layer; it converts business operations such as CreateInvoice into CRUD methods for consumption by the ORM. The VM is the ViewModel; it coordinates UI interaction with the SL/BLL. The V is the View; it contains surface-level UI interaction and validation code-behind.

Your sample code is suggestive of C#. In C#, your ORM (very likely Entity Framework) can produce code-generated entity classes that can be glued to domain logic using the partial keyword, allowing you to write custom domain and validation logic for each entity class without the pain of creating all that boilerplate for DTO's.

A lean architecture like this one should allow you to develop an easily maintainable application with the minimum necessary boilerplate code. Unless you're building an elaborate, crystal-cathedral architecture in Java for a large development team, this representative architecture should be all of the abstraction you will ever need.

  • Isn't glueing the domain model to the ORM bad practice for separation of concern, single responsibility principle, and encapsulation? – B1313 Oct 22 '17 at 17:23
  • @B1313: The best practice is the one that most effectively meets your specific software requirements. Ask yourself: what benefits do I get by duplicating functionality that I already have in another class? – Robert Harvey Oct 23 '17 at 3:21
  • @B1313 But your boilerplate code violates DRY and LEAN. The nice thing about software design principles is that, like "standards", there are so many of them. :-) Which is what Robert is getting at in his answer and comment. – user949300 Oct 31 '17 at 18:30
  • @user949300 Technically yes but only to demonstrate my concern with this being a good path. In reality my example would not actually violate DRY as I would not be duplicating effort (my domain model would be for a completely different reason than my data access model). The core of the issue with the current answers is that my example code is VERY pseudo code (i.e. requires a lot of reading beyond the example code) and not comprehensive enough to actually show my thinking. I planned on editing in a better example shortly after Robert posted his answer, but I just have not gotten around to it. – B1313 Nov 1 '17 at 3:01
  • @user949300 I will add a more comprehensive example that correctly shows what I am thinking as everyone has misinterpreted my current example (to no fault of their own, I just did not think to provide a fully detailed example as I did not think it was needed). I will edit in a new one tomorrow that much more detailed and comprehensive, this should explain my position MUCH better. – B1313 Nov 1 '17 at 3:05
4
  1. a)

Should my domain model contain properties similar to the underlying data model

No, this is almost never the case. It has not to be the case. Your objects don't have to store their data. Alan Kay, a man behind Smalltalk and what we now realize as OOP, wanted his objects to get rid of data. He proposed objects to have only a reference to the storage where its data is located. It is a consequence of a particular mindset concerning finding objects: they should be identified based on desired behavior, not data. So following this approach I've come to a conclusion that you simple don't need an ORM (besides it's simply horrible concept). That's how you can live without it.

b) Ideally, data should never be exposed to any object except its owner. Only behavior should be exposed. This is a fundamental OOP principle called encapsulation. Since I demonstrated how we can avoid data accessors for ORM to perform its job in a previous item, here is how you can avoid getters for UI.

c) Answering this item doesn't make sense in light of what I already wrote, but I understand it takes a bit of a mindset shift. So I'll answer in terms of DDD: one aggregate can contain a link to an identifier of another aggregate. It's just more easy to make them distributed if you need that, since aggregates represent good candidate seams to distribute your system along.

d) Since most of your objects will turn out to be immutable when applying an approach in item b, this doesn't make sense either. But if you're not ready yet for that -- setters should be private. No setters at all is even better, since setters are evil. If you need to mutate your data, expose it as a behavior and using technique in item b) you won't ever need to mutate object's state.

2.

a) You should create your objects using the keyword "new". This should be enough. No DI-container is needed. No public setters. If you insist on using an ORM -- ok, map the database column to object properties using data-mapper.

b) See examples here.

c) An always-valid camp camp is prevailing in ddd-community. More exotic ways to treat validation is with decorators. This technique appeared because of a Lego-brick metaphor used by David West. It states that there are basically two different stages of object lifetime: its creation and when it actually works. So nothing should prevent you from creating an object. All the behaviors, checks should be done on "working" phase. But I repeat it's quite an exotic approach.

  1. Something already has been told about validation. I'll list the links that were helpful for me: one, two, three, four.

  2. The question implies data-centric mindset. I think after reading the links I provided you'll have an idea what to do.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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