Search code examples
asp.net-mvcooparchitectureonion-architecture

Is it ok to have database context in my domain model


I am developing a web based application using ASP.NET MVC. I am trying have rich domain models rather than the thin/anemic models.

I have modelled my solution along the lines of Onion architecture. The different projects are as below : enter image description here

  • {}.Domain.Core - contains the domain objects and interfaces like IDbContext which is implemented in the Infrastructure layer
  • {}.Database - is the database prject
  • {].Infrastructure - contains implementation for logging, Data Access etc.
  • {}.Web - View and Controllers

**** The data access is done using dapper and IDbContext is a wrapper around 2 simple command, query interfaces. I have isolated each of the queries as separate class.

For sake of discussion I am taking a small part of the application.

I have a versioned document library which contains documents along with other metadata like tags, permissions etc

A simplified model of my document object is as shown below

enter image description here

I would want the operations to be defined within the domain object, since there is business logic involved in each of these operations. Let me take "Delete" as an operation. The operation needs to be perform

  • Validate if user has permission to delete
  • Check if there are no associations which will get impacted by this delete
  • Check if no workflow is in progress
  • Delete the actual item from database in a transaction

As shown in above example, I would need the database context to complete this operation. The way I have currently thinking if modeling is to have the domain object have IDbContext, which can execute the queries exposed.

enter image description here

In my controller class I call the domain objects and perform the operations.

I am not sure if passing the IDbContext in the domain object is ok? If not what are the better ways to model this?

I am not convinced in having a separate service layer because 1) Controller act as first layer of service layer for most of the cases 2) Service layer is just duplicating the same methods from domain to another class

Let me know how I can better this design.


Solution

  • Injecting the IDbContext like that brakes the main principle of the Domain model which should be responsible for business logic ONLY while retrieving and storing your domain entities is the responsibility of the infrastructure layer. Yes you inject it by interface, hiding the actual implementation but it makes you domain model aware of some storage.

    Also the steps from above required to delete a Document doesn't entierly belong to the Document object. Let's consider the first step with user permissions and the following cases:

    • Users with Admin role should be allowed to delete any document
    • Document owner should be allowed to delete the document

    For the first case there might not be a connection between a user and a document to remove. Admin users are just allowed to do anything. It's like a classical example with two bank accounts and an operation to tranfer money which involves both accounts but is not their responsibility. This is when Domain services come into place. Please don't confuse them with Service layer services. Domain services are part of the domain model and responsible for business logic only.

    So if I were you, I would create a new Domain service with DeleteDocument method. This should do the first three steps from above accepting User and Document as parameters. The fourth step should be done by your repository. Not sure what you mean by saying

    I didn’t see too much value in adding repositories

    but from domain model perspective you already have one it's the IDbContext. I assume you meant some pattern to implement repository or separate repository for each entity. In the long run your pseudo code in the controller should be the following:

    var user = bdContext<User>.SelectById(userId);
    var document = bdContext<Document>.SelectById(docId);
    var docService = new DocumentService();
    docService.DeleteDocument(document, user);  //  Throw exception here if deletion is not allowed
    bdContext<Document>.Delete(document);
    

    If you expect you need this logic in many places of you application you can just wrap it up in a Service layer service.

    I suggest reading Eric Evans book on DDD if you want to learn more about Domain modeling. This discusses the meaning of entities, value objects, domain services, etc. in detail.

    ANSWER TO THE COMMENT: Not really, the domain services are part of the domain, so both implementation and interface are part of the domain as well. The fact that two or more objects have to interact with each other is not enough for creating a domain service. Let's consider a flight booking system as an example. You have a Flight entity with different properties such as DepartureCity, ArrivalCity. Flight entity should also have a reference to a list of seats. Seat could be a separate entity as well with such properties as Class (business, economy, etc.), Type (isle, row, middle), etc. So booking a seat requires interacting with different entites, such as Flight and Seat but we don't need a domain service here. As by nature Seat property makes no sense if not considered as a child object of a Flight. It's even very unlikely you would ever have a case to query a Seat entity from out of the Flight context. So reserving a Seat is responsibility of the Flight entity here and it's ok to place the reserving logic to the Flight class. Please note it's just an example to try and explain when we need to create domain services, a real system could be modeled completely another way. So just try following these three basic steps to decide whether or not you need a domain service:

    1. The operation performed by the Service refers to a domain concept which does not naturally belong to an Entity or Value Object.
    2. The operation performed refers to other objects in the domain.
    3. The operation is stateless.

    I'm accessing dbcontext from the controller which is application/service layer not domain/business layer. Domain model deals with business logic only, it should not be aware of any persistance logic and from the example above you can see that DocumentService has no references of the dbcontext.