it:ad:ddd:layers:domain

IT:AD:DDD:Domain Layer

Summary

The Demain Layer exists to encapsulate a client's valuable Business/Domain logic/ip, without getting caught up in the technology needed to encapsulate it.

A quick check as what kind of logic to put here, versus Application, is to remember that when discussing with Business/Domain Experts, one doesn't usually talk about anything related to technology (Repositories, Authentication, Transactions, Web, Context, etc. – their eyes glaze over). Separation of Concern applies here: we should not be mixing our domain (Technology) with their domain (Business).

* As expressed more fully in Minimal Dependencies, keep your Dependencies minimal: * Prefer .NET Client Framework over .NET Full Framework * Remove all extraneous References in your Assemblies. * Consider using .NET35 versus .NET40

For example, the Domain Layer is not dealing with data storage (that's the Infrastructure (Data Implementation) Layer's responsibilty) so System.Data can be removed.

In addition, the Domain Layer is not dealing with serialization (that's the Distributes Services Layer's responsibility) so System.Xml can be removed.

In fact, as a primary design requirement of a Domain Layer is for it to remain as technology agnostic as possible, a Domain Layer really shouldn't need much more than System.Core, where string, int, enum, etc. are defined.

The same rules apply to DDDD based applications as older architectures: Assembly should only reference Assemblies in layers below or sideways to the current layer – never upwards – in order to avoid circular references.

So, after removing extraneous References, the Domain Layer assemblies has References to: * Domain Entities Layer Assemblies

  • Rational: Domain works with Domain Entities (makes sense enough) but the Entities are moved out of the Domain Assembly and into a separate assembly for reasons better expressed in the App.Domain.Entites summary.

* XAct.Data

  • Rational: XAct.Data contains the Interface/Contract for IRepository. XAct.Data on the other hand does not contain an implementation of it, so it won't be dragging any IDbConnection or other technical specification into the Domain Layer.*

Contrast this with how the Infrastructure (Data Implementation) Layer has a Reference to both the same XAct.Data, as well as XAct.Data.Db and maybe XAct.Data.Db.EF as well, as it will need the to have in implementation as well as the contract for a Repository.

In DDD, a key concept is to work with Entities, objects that encapsulate Domain concepts, without any reference to the actual technology used to persist them, so you won't find any reference to DataSet, DataReader, IDbRecord or any other Db technology specific objects in the Domain or App.Domain.Entites

As stated previously, the Domain Entities are defined in a separate assembly (App.Domain.Entites) but they are essential for discussion of the next part: Domain Repositories.

Also, with the advent of Code First and other EF ways of creating Code-First Domain Entities, it's become common to see Anemic Entities. Martin Fowler made a specific point to state that Anemic Entities missed the point of OOP.

Entities should not be just Property get/setters. They should have Functionality as well.

That said, Martin Fowler was talking about programming in general, not specifically what .NET and its own specific elegant .NET language features can provide, namely IT:AD:Patterns:Extension Methods Strategy.

Therefore, yes, Domain Entities should have functionality – but to clearly separate between Message and Behaviour, Domain Entity Functionality should be provided as IT:AD:Patterns:Extension Methods Strategy.

Another point: behaviour/methods should provide functionality that concerns that single object – never reference another Aggregate Entity type. In other words Validate() is an appropriate method to give to an entity, but BankEntity.TransferTo(BankEntity bankEntity) is not. That's what a Domain Service is for (see below).

Unlike older DAL patterns, Repositories are not Data Layer concepts – but are (as Martin Fowler's definition of them expresses them) clearly Domain concerns.

What's meant by that is that a Repository Interface\Contract is defined herein the Domain Layer, and its method signatures are tech-agnostic (no mention of IDbConnection, EF, WCF or other), dealing in Domain Entities.

Their implementation on the other hand of course is not done here (that would only drag technical References into these assemblies which is exactly what we are trying to avoid), but done in the Infrastructure (Data Implementation) Layer.

This is very important to understand: a repository is a contract for how to get/persist data entities…how it's done is none of the business of a BA and hence has no reason being in the Domain Layer.

Note: Domain Repositories are often discussed separately to Domain Services (see below), but if properly designed, the distinction between Repositories and Domain Services is artificial, and only representative of how important Repositories are to most apps: they are, if you will, First among Equals, but stateless Services none the less.

Other than Domain Repositories, the Domain Layer can also contain Domain Services, as needed.

I clearly bring attention to the term as needed because they are needed under specific conditions.

Domain Services are:

* Services and therefore stateless.

  • They are stateless, but can of course set global application state.

* There to perform operations that are not intrinsic to a single Domain Aggregate Entities. * This generally means to provide Coordination of operations across multiple Aggregate Domain Entities – even if the same type. The classic example is a bank tranfer between two entities:


TransferService:Transfer(Account accntA, Account AccntB, decimal amount)
{
   accntA.Add(amount);
   accntB.Debit(amount);
}

* As mentioned above under Domain Entity Functionlity, *TransferTo(...)* or *TransferFrom(...)* would *not* be a good Entity behaviour/method, as it involves another Entity (even if the same Type) that might contain extensive validation, etc. that the first entity might not be aware of.

* In most cases Domain Services try to do their Domain stuff without accessing Domain Repositories directly…that's a bit too close to Infrastructure for comfort.

  • It's generally the role of the invoking Application Service to collect from the Repositories the Entities needed, and pass them to a Domain Service to orchestrate.
  • But that's not a hard/fast rule. Exceptions can be made:
  • * But if it has to be done, it should really only be done for Querying for State/Options/Lookup stuff, and not updating Entities values.
  • * Could be getting cached values (lookups)
  • * Could be getting query results (Sum(X)) or Sum(Y) depending on flow of business process.

Giving something an appropriate name is important to correctly encapsulate the Concern it is addressing, and by extension, stop the potential adding of behavior that should not be included in the same class.

Domain Services are often named after a Business Domain Operation – and not an Domain Aggregate Entity.

Examples would be:

  • eg: TransferService, not BankAccountService
  • eg: BillingService, not InvoiceService
    • if that feels a bit artificial InvoicingService might be still better than InvoiceService, which is too close to getting fixated on the Invoice Entity.
  • eg: PurchaseService, not ShoppingCartService
  • eg: VisaCheckService, not VisaInfoRequestService

Domain Service Operations come from the BizDomain Operations, in their lingo. By convention, they are:

  • Verb focused, not Noun focused (eg: GetInvoice)

The list is probably too long, as the Domain layer is probably the most tightly focused the various layers, but a quick summary would be:

  • Anything Technical: Basically, if your code is starting to reference ITracing, Caching,UoW, transactions, etc… something is probably going wrong. See what you can factored out to App.Application.
  • Repositories: That one depends. See below.
  • Mapping. The domain should not be dealing with mapping between different types – by the time it has got an entity from the Repository, it should be already mapped from the incoming IDataRecord, JSON object or WCF soap object to a Domain Entity. Nor should it be doing any mapping to communication objects to a Client layer.

It is a known fact that most web applications DO invoke Repository from Domain Layer Services. It's less costly than signalling breaking in and back out to an Application Layer.
But invoking Repositories (Infrastrucutural) quickly leads to lax Separation of Concerns, and it's only a quick skip and a hop before one is dragging in:

  • Tracing (Infrastructural),
  • AAA (infrastructural),
  • Exception handling (infrastructural),
  • DTO (infrastructural),
  • and next thing you know…you have a BizDal layer: a clusterf*!#! of Domain + Infrastructure, and the only redeeming feature it has over older BizDal based apps is that it doesn't have DAO's in it as well.
  • If Domain Service Operations are supposed to remain technology agnostic, are they suppossed to avoid ITracingService, IAuthorisationService, etc?
    • One part of the answer would be: Domain Services are close cousins to Domain Entity Methods. Would you put tracing within a Domain Entity get/set? Probably not. Would you put invoke AuthorisationService from there as well? The answer is …probably not.
      • A demonstration of this – starting with the simpler of the two cases: invoking of a Domain Entity method – would be the invoking of the entity's method by an Application Services – which would first orchestrate the call to Infrastructure Services to ensure the current IIdentity has authorisation to perform the operation, as well as Trace/Log the result of the operation.
      • The same applies for the DomainService: ensure the invoking Application Layer Service does the AAA (Authentication, Auth, Auditing before invoking the Domain Service method.
      • A caveat: sometimes the Domain Service Operation/Method needs to lookup data. It could be needing lookup data from a Repository, or needing to know whether the current user has xtra permissions, based on the return result of the Repository lookup. Whatever the reason, I've seen several solutions to this dilema:
      • * break the rule. Heck…
      • * provide Func<> arguments for the Domain Service to callback.
  • /home/skysigal/public_html/data/pages/it/ad/ddd/layers/domain.txt
  • Last modified: 2023/11/04 03:40
  • by 127.0.0.1