26
public class Person
{
    public IList<String> SpecialBirthPlaces;
    public static readonly DateTime ImportantDate;
    public String BirthPlace {get;set;}

    public DateTime BirthDate
    {
        set
        {
            if (BirthPlace!=null && 
                value < ImportantDate && 
                SpecialBirthPlaces.Contains(BirthPlace))
            {
                BirthPlace = DataBase.GetBirthPlaceFor(BirthPlace, value);
            }
        }
    }
}

This is an attempt to encapsulate a simple rule in my domain model. The rule I'm trying to capture is: when, for some reason, we update a person's birth date (e.g. there was a mistake in the original user input) we need to check the person's birthplace and replace it with some other value from a database, if it is listed in our database as a special birthplace.

However, I have 2 problems implementing it:

  1. This rule modifies domain entity state (property) and I need to reflect this change in the user interface. My domain model is POCO. I could put this logic in the ViewModel, but this is wrong because it's not UI logic. It's an important domain rule which I need to capture.

  2. My list of SpecialBirthPlaces is pretty big and I don't want to populate it every time I get a customer from database. Also, I need to get a replacement for Birthplace when the rule is satisfied. As I said the list of special birthplaces and replacements for this is very big and is stored in the DB.

How to implement the logic I need in DDD style?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Alex Burtsev
  • 12,418
  • 8
  • 60
  • 87
  • I don't really understand the question. Are you asking how to cache a large list of items (SpecialBirthPlaces) or how to persist changes to a domain model? from a DDD perspective it seems that the Bounded Context is the collection of rules around a person's birthdate and place, and the Person aggregate root needs to have a method that allows the date and place to be changed. The change to the date and place of birth would trigger an event that would result in updating the datastore. I maybe totally off, as I'm new to DDD myself. – Jason Nov 25 '11 at 16:20
  • 2
    Were you trying to achieve showing "Leningrad" as birthplace if the birth place is before 1991 and "St. Petersburg" after? – abuzittin gillifirca Jun 04 '14 at 06:44
  • little late, but i am on the same situation and i am stack. Did you manage to find a valid solution ? or at least some formal approach regarding ddd? – Stelios Mar 08 '21 at 08:30

4 Answers4

4

The way I've encapsulated this problem, which is modification tracking, is with the Unit of Work pattern. I have my DDD repositories associated with a unit of work, and I can query the unit of work for any set of entities which I get from any of the repositories to see which are modified.

As for the large collection, it appears to be a read-only set. One way to handle this is to preload and cache this locally if it is ever accessed, then the repositories can run queries against the in-memory version. I use NHibernate, and it is easy to handle this case with it. If it is way too big to store in RAM (like 100s of MB or more), you'll probably need to special case repository queries against it, so that the SpecialBirthPlaces.Contains(BirthPlace) query is executed on the database (perhaps in a stored proc, ha!). You'd probably want to express SpecialBirthPlaces as a repository of entities, rather than just a big collection of strings, which would allow the "Query" pattern to free you from needing to load the entire thing.

After this lengthy narrative, here's some example:

public class BirthPlace
{
    public String Name { get; set; }
} 

public class SpecialBirthPlace : BirthPlace
{
}

public class Person 
{
    public static readonly DateTime ImportantDate;
    public BirthPlace BirthPlace { get; set; } 

    public DateTime BirthDate 
    { 
        get; private set;
    } 

    public void CorrectBirthDate(IRepository<SpecialBirthPlace> specialBirthPlaces, DateTime date)
    {
        if (BirthPlace != null && date < ImportantDate && specialBirthPlaces.Contains(BirthPlace)) 
        { 
            BirthPlace = specialBirthPlaces.GetForDate(date); 
        }
    }
} 

Having a method where you pass in the corrected birth date is a better design since it tells you via the parameters what is needed to actually correct the birth date: a repository (i.e collection) of SpecialBirthPlace entities and the correct date. This explicit contract makes it clear what the domain is doing, and makes the business needs clear just by reading the entity contracts, where putting the whole collection in the state of the entity hides it.

Now that we've made BirthPlace into an entity, we can see that there may be one more optimization to make the domain model a bit flatter. We don't really need to specialize BirthPlace but we do need to indicate if it is special. We can add a property to the object (some people begrudge properties on domain objects, but I don't, since it makes queries easier, especially with LINQ) to indicate if it is special. Then we can get rid of the Contains query altogether:

public class BirthPlace
{
    public BirthPlace(String name, Boolean isSpecial = false)
    {
        Name = name;
        IsSpecial = isSpecial
    } 

    public String Name { get; private set; }
    public Boolean IsSpecial { get; private set; }
}

public class Person 
{
    public static readonly DateTime ImportantDate;
    public BirthPlace BirthPlace { get; set; } 

    public DateTime BirthDate 
    { 
        get; private set;
    } 

    public void CorrectBirthDate(IRepository<BirthPlace> birthPlaces, DateTime date)
    {
        if (BirthPlace != null && date < ImportantDate && BirthPlace.IsSpecial) 
        { 
            BirthPlace = birthPlaces.GetForDate(date); 
        }
    }
} 
codekaizen
  • 26,990
  • 7
  • 84
  • 140
  • 1
    this is close to what I was thinking of, but I don't like that we can't simply set Birthdate, but nothing should prevent us to do so if BirthPlace is not set yet (is null) – Alex Burtsev Nov 25 '11 at 18:53
  • Do that in the constructor. That's the only DDD alternative. Your business rules dictate that you can't "just set it", so don't try to force a technical idiom where it would break the domain. – codekaizen Nov 25 '11 at 18:59
  • I'm still a little uncomfortable about inability to set BirthDate if we don't need to update BirthPlace, what do you think about this modification of your code? http://pastebin.com/aF7v0a4z, sure throwinf exception is not the way to go, but I can't think of anything alse at the moment. So consumers of the model should first check check if CorrectBirthDate call is required. – Alex Burtsev Nov 25 '11 at 19:28
  • @Alex - Yea, that's not so bad. The exception is reasonable, I think, but does have disadvantages. I just think the best design is that you require the birth places repo when setting the birth date, since it is simple and straightforward to consume this from a viewmodel. – codekaizen Nov 25 '11 at 19:55
  • The idea of something is wrong here doesn't abandon me, I think that Entities should not have access to repositories. I think we should think of this situation from other point of view. See this SA topics about repositories in entity: http://stackoverflow.com/q/827670/235715 http://stackoverflow.com/q/5694241/235715 – Alex Burtsev Nov 27 '11 at 08:29
  • 1
    IMO, this answer is a 'good DDD' solution. I've used similar numerous times, and it works well. The only change I'd make is to replace the `IRepository` with a different interface, e.g. `IBirthPlaceService`(better name required), as I think the Repository couples it to a technical decision. The entity doesn't need to care where/how, that's an implementation detail. – jasper Feb 17 '15 at 17:11
  • 1
    @jasper I would reason that the repository interface is fine. The "service" suffix makes it too obscure: "Give me a thing that can do all sorts of things related to birth places." Oh dear, what is is going to do with that? "Repository" is accurate: "Give me a thing that allows me to retrieve birth places." Note that the word repository (synonym: storehouse) is not a technical term per se, and makes sense from a domain perspective just as well. – Timo Nov 16 '16 at 14:00
3

I think the statements "I need to reflect this change in the user interface" and "It's an important domain rule which I need to capture" describe two different problems. Clearly, the first one needs to be solved; it isn't clear that the second one does.

If other parts of your domain model need to know about changes here, you would do well to have a look at Domain Events (for example, Udi Dahan's implementation). You could also use this to set the BirthPlace property when the BirthDate gets set, even asynchronously if it is a potentially lengthy operation.

Otherwise, let's just look at the UI issue. First of all, in my domain model, I would have each entity abstracted as an interface. If you don't, then you may need to at least make some properties virtual. I'd also be using a layer of abstraction for generation/returning my entities, such as IoC/factory/repository. I consider this layer to be outside the bounds of the domain model itself.

Now, we need a mechanism to notify the UI of changes to properties in domain entities, but of course the domain model itself is in a sense a closed system: we don't want to introduce new members or behaviours to satisfy the needs of any outside concern.

What if we decorate the entity in question with an implementation that implements INotifyPropertyChanged? We could do this in our repository, which we've established is outside the bounds of the domain, so we would not be modifying the domain model itself, only using composition to wrap the entities with functionality that the system outside the domain model needs. To restate, the recalculation of BirthPlace remains a concern of the domain model, while the UI notification logic remains a concern outside of the domain model.

It would look something like this:

public class NotifyPerson : IPerson, INotifyPropertyChanged
{
    readonly IPerson _inner;

    public NotifyPerson(IPerson inner) // repository puts the "true" domain entity here
    {
        _inner = inner;
    }

    public DateTime BirthDate
    {
        set 
        {
            if(value == _inner.BirthDate)
                return;

            var previousBirthPlace = BirthPlace;
            _inner.BirthDate = value;
            Notify("BirthDate");

            if(BirthPlace != previousBirthPlace) 
                Notify("BirthPlace");
        }
    }

    void Notify(string property)
    {
        var handler = PropertyChanged;
        if(handler != null) handler(this, new PropertyChangedEventArgs(property));
    }
}

If not using interfaces, you would simply inherit from Person and override the BirthDate property, calling members on base instead of _inner.

Jay
  • 56,361
  • 10
  • 99
  • 123
  • What you wrote looks exectly like ViewModel, wraping Model. However I got the idea that we check BirthPlace if it was changed after setting BirthDate. I think this hides important information that a change in BirthDate could result in a BirthPlace change. And some kind of explicit event on entity like domain event will make this fact more noticable to developers. – Alex Burtsev Nov 25 '11 at 19:20
  • @AlexBurtsev A ViewModel is adapted for a specific view. This object on the other hand would be used *as the entity* throughout the application. The benefit it confers is only to keep the domain model pristine. The behaviour is not hidden here; it is hidden in the model, which is why I think domain events are a good alternative solution. You'll still have the problem of updating the UI, though. You'll either have to do something like I've shown here or subscribe to domain events from outside the domain model. – Jay Nov 25 '11 at 19:36
  • And what's wrong with subscribing to domain events from outside the domain? Is it bad practice? – Alex Burtsev Nov 25 '11 at 19:39
  • @AlexBurtsev No, I think it is fine. – Jay Nov 25 '11 at 20:13
  • The problem is that PropertyChanged is not a domain event. Notifying listeners when property changed is not a domain logic, it is an application's logic. And I don't think NotifyPerson should do anything except pass control to true entity and then notify listeners. `if(value == _inner.BirthDate) return;` It can't know what BirthDate setter is doing, what if it counts attempts to change birth date? :) . I'm not sure if NotifyPerson should know which properties is affected by method, but may be it is ok. – Flashrunner Jul 20 '16 at 15:33
  • I like this solution. I can use application-specific PersonFactory that implements domain IPersonFactory by wrapping domain-specific PersonFactory and returning NotifyPerson. And if Person need to update OtherAggregate then it updates its wrapper, NotifyOtherAggregate, that it got from application-specific OtherAggregateFactory (or Repository). – Flashrunner Jul 20 '16 at 15:33
2

The following is a sample implementation. This implementation consists of several layers: the domain layer, the service layer and the presentation layer. This purpose of the service layer is to expose the functionality of your domain layer to other layers, such as the presentation layer or a web service. To that end, its methods correspond to specific commands that can be processed by the domain layer. In particular we have the command to change the birthday. Furthermore, this implementation uses Udi Dahan's version of a domain event framework. This is done to decouple the domain entity from the business logic associated with changing the birthday. This can be regarded as both a benefit and a drawback. The drawback is that your overall business logic is spread across multiple classes. The benefit is that you gain a lot of flexibility in how you handle domain events. Additionally, this approach is more extensible, since you can add subscribers to the BirthDateChangedEvent which perform auxiliary functions. Yet another benefit, (which contributed to the reasoning behind Udi's implementation) is that your Person entity no longer needs to be aware of any repositories, which seem outside the scope of the domain entity. Overall, this implementation calls for quite a bit of infrastructure, however if you envision investing greatly into your domain then it is worth the initial trouble. Also note, that this implementation assumed an ASP.NET MVC based presentation layer. In a stateful UI, the presentation logic would need to change and the ViewModel would need to provide change notifications.

/// <summary>
/// This is your main entity, while it may seem anemic, it is only because 
/// it is simplistic.
/// </summary>
class Person
{
    public string Id { get; set; }
    public string BirthPlace { get; set; }

    DateTime birthDate;

    public DateTime BirthDate
    {
        get { return this.birthDate; }
        set
        {
            if (this.birthDate != value)
            {
                this.birthDate = value;
                DomainEvents.Raise(new BirthDateChangedEvent(this.Id));
            }
        }
    }
}

/// <summary>
/// Udi Dahan's implementation.
/// </summary>
static class DomainEvents
{
    public static void Raise<TEvent>(TEvent e) where TEvent : IDomainEvent
    {
    }
}

interface IDomainEvent { }

/// <summary>
/// This is the interesting domain event which interested parties subscribe to 
/// and handle in special ways.
/// </summary>
class BirthDateChangedEvent : IDomainEvent
{
    public BirthDateChangedEvent(string personId)
    {
        this.PersonId = personId;
    }

    public string PersonId { get; private set; }
}

/// <summary>
/// This can be associated to a Unit of Work.
/// </summary>
interface IPersonRepository
{
    Person Get(string id);
    void Save(Person person);
}

/// <summary>
/// This can implement caching for performance.
/// </summary>
interface IBirthPlaceRepository
{
    bool IsSpecial(string brithPlace);
    string GetBirthPlaceFor(string birthPlace, DateTime birthDate);
}

interface IUnitOfWork : IDisposable
{
    void Commit();
}

static class UnitOfWork
{
    public static IUnitOfWork Start()
    {
        return null;
    }
}

class ChangeBirthDateCommand
{
    public string PersonId { get; set; }
    public DateTime BirthDate { get; set; }
}

/// <summary>
/// This is the application layer service which exposes the functionality of the domain 
/// to the presentation layer.
/// </summary>
class PersonService
{
    readonly IPersonRepository personDb;

    public void ChangeBirthDate(ChangeBirthDateCommand command)
    {
        // The service is a good place to initiate transactions, security checks, etc.
        using (var uow = UnitOfWork.Start())
        {
            var person = this.personDb.Get(command.PersonId);
            if (person == null)
                throw new Exception();

            person.BirthDate = command.BirthDate;

            // or business logic can be handled here instead of having a handler.

            uow.Commit();
        }
    }
}

/// <summary>
/// This view model is part of the presentation layer.
/// </summary>
class PersonViewModel
{
    public PersonViewModel() { }

    public PersonViewModel(Person person)
    {
        this.BirthPlace = person.BirthPlace;
        this.BirthDate = person.BirthDate;
    }

    public string BirthPlace { get; set; }
    public DateTime BirthDate { get; set; }
}

/// <summary>
/// This is part of the presentation layer.
/// </summary>
class PersonController
{
    readonly PersonService personService;
    readonly IPersonRepository personDb;

    public void Show(string personId)
    {
        var person = this.personDb.Get(personId);
        var viewModel = new PersonViewModel(person);
        // UI framework code here.
    }

    public void HandleChangeBirthDate(string personId, DateTime birthDate)
    {
        this.personService.ChangeBirthDate(new ChangeBirthDateCommand { PersonId = personId, BirthDate = birthDate });
        Show(personId);
    }
}

interface IHandle<TEvent> where TEvent : IDomainEvent
{
    void Handle(TEvent e);
}

/// <summary>
/// This handler contains the business logic associated with changing birthdates. This logic may change
/// and may depend on other factors.
/// </summary>
class BirthDateChangedBirthPlaceHandler : IHandle<BirthDateChangedEvent>
{
    readonly IPersonRepository personDb;
    readonly IBirthPlaceRepository birthPlaceDb;
    readonly DateTime importantDate;

    public void Handle(BirthDateChangedEvent e)
    {
        var person = this.personDb.Get(e.PersonId);
        if (person == null)
            throw new Exception();

        if (person.BirthPlace != null && person.BirthDate < this.importantDate)
        {
            if (this.birthPlaceDb.IsSpecial(person.BirthPlace))
            {
                person.BirthPlace = this.birthPlaceDb.GetBirthPlaceFor(person.BirthPlace, person.BirthDate);
                this.personDb.Save(person);
            }
        }
    }
}
eulerfx
  • 36,769
  • 7
  • 61
  • 83
  • 9
    I like your implementation, it is a good code, but it's not DDD. What your are doing here is borrowing infrastructure, and technical aspects from DDD, without using domain model itself. And I see people do it a lot. Their Domain logic is scattered around in Services, and their domain Entities are just DTO's. And I can't blame them, this is a legitimate pattern in my opinion, because real world systems need to deal with transactions, external services, and domain logic isn't that simple as it shown us in DDD examples. – Alex Burtsev Dec 02 '11 at 06:39
  • 5
    DomainEvents.Raise is just a fancy name for ServiceLocator.Resolve ().GetBirthPlaceFor (...) – Michael Logutov Jun 25 '13 at 12:43
-1

IMO the best approach performance-wise would be to create a stored procedure in your db and mark entity on property changed event to invoke it when committing changes to db (SaveChanges() call). ObjectContext.ExecuteFunction is your friend in that case.

Put all your logic of birthplace lookup and update in that sproc. Make sure sproc is included in the transaction - so that changes are rolled back if update fails.

Edit: Sorry for not DDD related answer.

Paul
  • 1,879
  • 1
  • 23
  • 44
  • 4
    Sorry, Paul but you answer is actually 100% incorect for my case and thats very funny -) First this have nothing to do with DDD. 2. I'm using Object Database which doesn't have stored procs, my entity is POCO object and doesn't implement INotifyPropertyChanged, I do not use ObjectContexts (EF), Invoking stored proc on propety changed event is the scariest thing I have ever heard. But Paul, you really cheared my up, I was very depressed before I read you answer, thank you. – Alex Burtsev Nov 25 '11 at 18:18
  • Glad I cheered you up! I was not actually suggesting pulling sproc from onPropertyChangedEvent handler - that would be scary in fact. The point was to actually do a look-up at db level (considering bd is changed rarely there is no need to keep your list in memory) and perform operation when saving the data to db (in the same transaction). – Paul Nov 25 '11 at 18:50
  • 1
    @Paul - the idea of doing the lookup on the DB side is reasonable, of course, since it really is just a 'contains' query, but the approach you recommend is counter to the domain driven approach, which makes the case that putting all the decision logic in the objects themselves instead of in various layers, like the DB, allows us to create very clear, maintainable software. – codekaizen Nov 25 '11 at 19:05
  • Isn't it sadly funny? Putting the logic in a stored procedure would solve so many problems: no matter what code or even *which server* the change comes from, the required corresponding logic always happens... But we would be putting half of our business rules in the stored procedure. – Timo Nov 16 '16 at 14:19