What is the best practice to deal with navigation

2020-08-19 05:25发布

问题:

I'm trying to wrap my head around Domain Driven Development. I want to make sure I have a good foundation and understanding of it, so it would be great if recommendations to use AutoMapper or similar are avoided here. My architecture currently involves the following:

  • The WCF service is responsible for persistence (using Entity Framework) and server-side validation. It converts POCO's to DTO's, and DTO's are transferred to the client.

  • The Client, receives DTO's and converts them to POCO's. The class that converts POCO's and DTO's is shared between the service and the client.

  • The POCO's implement IValidatableObject and INotifyPropertyChanged and are used by both the server and the client, but they are not used for data transfer. The DTO's are, which are just property bags containing no behavior.

(1) Question #1. Is this architecture appropriate for a Domain Driven Design.
(2) Question #2. Is it appropriate for POCO's to contain navigation properties? It really feels wrong for POCO's to contain navigation properties in a DDD architecture to me, because it doesn't make sense to me to have a navigation property that may or may not be serialized. It would make more sense to me to have a specialized DTO.

For example, here is a POCO/DTO looks like in my architecture.

// Enforces consistency between a POCO and DTO
public interface IExample
{
    Int32 Id { get; set; }
    String Name { get; set; }
}

// POCO
public class Example : IExample, INotifyPropertyChanged, IValidatableObject
{
    private int id;
    private string name;

    public Int32 Id {
        get { return this.id; }
        set {
            this.id = value;
            OnPropertyChanged("Id");
        }
    }

    public String Name {
        get { return this.name; }
        set {
            this.name = value;
            OnPropertyChanged("Name ");
        }
    }

    public ICollection<Example2> ChildExamples {
        get { ... }
        set { ... }
    }

    // INotifyPropertyChanged Members
    // IValidatableObject Members
}

// DTO
public class ExampleInfo : IExample
{
    public Int32 Id { get; set; }
    public String Name { get; set; }
    public ICollection<Example2Info> ChildExamples { get; set; }
}

It doesn't seem right though, because you may not always need the navigation property, and having an empty (null) object (or collection) seems very wrong in an object-oriented architecture. You also have to deal with serializing and converting deep object hierarchies at times, which is not trivial. It would make more sense for a specialized DTO so there isn't a problem with the constant possibility of empty navigation properties that may or may not need serialized or populated.

public class ComplexInfo
{
    public Example ExampleInfo { get; set; }
    public ICollection<Example2Info> ChildExamples { get; set; }
}

How are these situations handled in real-world enterprise DDD style architectures and what other advice can be given here?

回答1:

I agree with Jehof about sending the DTO's to your client and keeping the domain model clean on the server side under your WCF.

With respect to navigation properties, one point Eric Evans emphasizes in Domain Driven Design is to respect invariants. So, in your example above ask yourself if Id and Name are really going to change in the lifetime of the object, or are they invariants? A lot of DDD-style developers would not even put a setter on those properties. Instead build the object's invariant state through a constructor. If Name can change, you probably want a method called Rename(string newName), because there's probably some kind of business rules you'd want to put there anyway.

A red flag in your layers above is that you have your whole object model in the DAL. What you call your assemblies really isn't a big deal but I think it points to your tendency to keep thinking of the application from a data perspective. The point of DDD is to think of your object model in terms of logic and behavior, not data and structure. I (and most other DDD developers, I think) think of the data access layer as Repository classes which return Aggregate Roots. The repositories are responsible for returning your hydrated poco/entity objects from the DAL(repository) to the business layer (and above, such as an application/service layer class or your WCF in your above example). In your case of using EF, you'd have the repositories wrap your DataContext calls and return the entity objects.

I could go on and on, because your question is really targeting the basic fundamentals of DDD, of which there are several. I would recommend 1) Read Eric Evans book, "Domain Driven Design". 2) Keep in mind that DDD targets complex business software. If you're trying to apply it to a simple CRUD application which really is just UI forms and data binding to DB tables, its hard to see a DDD approach take shape, because the problems it addresses just aren't there. So keep that in perspective.



回答2:

Is this architecture appropriate for a Domain Driven Design?

Not entirely. Take a look at hexagonal architecture for a description of a more modern architectural style which fits nicely with DDD. Within hexagonal, your domain is at the core and various components "attach" to it. For example, a WCF service would be considered an adapter in a hexagonal architecture because it adapts your domain to a communication technology such as TCP or HTTP. Typically, you would have an application service which establishes a facade over your domain and effectively represents use cases. This application service can be referenced by a WCF service to expose functionality over HTTP. Unfortunately, the "service" terminology can be a bit conflating.

Is it appropriate for POCO's to contain navigation properties?

It is appropriate, but the right answer is that it depends. One of the issues with navigational properties that you state is that they may or may not be serialized for a specific DTO. This is telling me that you are talking about queries. Some queries need only a subset of attributes on an aggregate/entity (POCO) and thus the corresponding DTO only has those required properties. It seems wasteful to retrieve an entire entity together with navigational properties. To address this issue you can employ lazy loading. A more salable approach however, is to use read-models for queries. Also, as stated by others, an entity/aggregate certainly can and should contain navigational properties if they are a reflection of the domain. How these "navigational" properties are implemented can vary. Sometimes it can be better to split an aggregate into multiple aggregates. Take a look at Effective Aggregate Design by Vaughn Vernon.

As pointed out by Jehof, you should try to have clients of the WCF service only depend on the contract of that service itself, not on the domain entities (POCOs) that the service encapsulates. Typically, POCOs should not implement INotifyPropertyChanged and IValidatableObject because those interfaces support UI concerns and should be handled by the DTOs or ViewModels.



回答3:

Domain Driven Design isn't about POCO's or DTO's. It's about Entities, Aggregate Roots, Value Objects. About rich domain objects that can encapsulate behavior in addition to data.

Is it appropriate for POCO's to contain navigation properties ?

It's not clear to me what the POCOs are for in your scenario, but if they are your domain entities, then they can and should certainly contain navigation properties. Actually, using the navigation properties of an Aggregate Root (a special kind of domain entity) is often the only way for external objects to access entities enclosed in that Aggregate. Navigation through association properties is a key concept in DDD.

Also, the recommended architecture in DDD looks more or less like :

  • Presentation Layer (UI)
  • Application layer
  • Domain Layer
  • Infrastructure layer (includes persistence/DAL)

The key here is the Single Responsibility Principle. You don't want a service that does persistence, server-side validation and DTO mapping at the same time. You need decoupling. You need a clear distribution of responsibilities among your layers so that they are more easily maintainable, extensible and portable.



回答4:

Another suggestion: think very hard whether to share the mapping code (and by implication the classes they are mapped to) between the client the server. There is nothing wrong with sharing code, but be careful you are not mixing client concerns and server concerns. It may start with small compromises "I need this property only on the client, but everything else is the same", but you might end up with flags to tell the class whether to use client or server behavior and other nastiness. Having separate implementations of the POCO's may seem as code duplication at first, but it frees you to have an implementation fitted to the task. That's why using Automapper and the like makes sense, it lowers the barrier of writing the mapping code.

Another reason to do this (which has been also mentioned) is that the DTO's should be a way to implement a communication API, and not the API itself: i.e. the DTO's are there for WCF to implement a SOAP API (or REST or whatever), but the client should be free to implement the communication layer using only the API specification, without any hidden logic in the mapping code.

This also ensures your API remains language agnostic. You might want to provide client libraries (in any of several appropriate languages) to ease the interaction with your API, but these should not be a requirement.