Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put Created date and Created by in DDD?

I use Entity Framework and want to use DDD principles. However, there are some information regarding the entities that is on the borderline between what is logging/persistence information and what is information about the domain objects.

I my situation these are put in an abstract base class that all entities inherit from:

 public abstract class BaseEntity: IBaseEntity
{

    /// <summary>
    /// The unique identifier
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// The user that created this instance
    /// </summary>
    public User CreatedBy { get; set; }

    /// <summary>
    /// The date and time the object was created
    /// </summary>
    public DateTime CreatedDate { get; set; }

    /// <summary>
    /// Which user was the last one to change this object
    /// </summary>
    public User LastChangedBy { get; set; }

    /// <summary>
    /// When was the object last changed
    /// </summary>
    public DateTime LastChangedDate { get; set; }

    /// <summary>
    /// This is the status of the entity. See EntityStatus documentation for more information.
    /// </summary>
    public EntityStatus EntityStatus { get; set; }

    /// <summary>
    /// Sets the default value for a new object
    /// </summary>
    protected BaseEntity()
    {
        CreatedDate = DateTime.Now;
        EntityStatus = EntityStatus.Active;
        LastChangedDate = DateTime.Now;
    }

}

Now a Domain Object can't be instantiated without providing the date and time. However, I feel it is the wrong place to put it. I can argue for both really. Maybe it should not be mixed with the domain at all?

Since I'm using EF Code First it makes sense to put it there, or else I would need to create new classes that inherit from the base class in the DAL also, duplicating code and needing to map to both domain objects and MVC models which does seem more messy than the approach above.

The question(s):

Is it Ok to use DateTime.Now in the Domain model at all? Where do you put this kind of information using DDD and EF Code First? Should User to be set in the domain object or require it in the Business Layer?

Update

I think jgauffin har the right answer here - but it is really quite a fundamental change. However, on my search for an alternate solution I almost had it solved with this. I used the ChangeTracker.Entries to find ut if an entity is added or modified and set the fields accordingly. This is done in my UnitOfWork Save() method.

The problem is loading navigation properties, like User (DateTime is set correctly). It might be since the user is a property on the abstract base class the entity inherits from. I also don't like putting strings in there, however it might solve some simple scenarios for someone, so I post the solution here:

        public void SaveChanges(User changedBy)
    {
        foreach (var entry in _context.ChangeTracker.Entries<BaseEntity>())
        {
            if (entry.State == EntityState.Added)
            {
                entry.Entity.CreatedDate = DateTime.Now;
                entry.Entity.LastChangedDate = DateTime.Now;
                entry.Entity.CreatedBy = changedBy;
                entry.Entity.LastChangedBy = changedBy;
            }
            if (entry.State == EntityState.Modified)
            {
                entry.Entity.CreatedDate = entry.OriginalValues.GetValue<DateTime("CreatedDate");
                entry.Entity.CreatedBy = entry.OriginalValues.GetValue<User>("CreatedBy");
                entry.Entity.LastChangedDate = DateTime.Now;
                entry.Entity.LastChangedBy = changedBy;
            }
        }


        _context.SaveChanges();
    }
like image 385
cfs Avatar asked Oct 17 '12 18:10

cfs


2 Answers

Is it Ok to use DateTime.Now in the Domain model at all?

Yes.

Where do you put this kind of information using DDD and EF Code First? Should User to be set in the domain object or require it in the Business Layer?

Well. First of all: A DDD model is always in a valid state. That's impossible with public setters. In DDD you work with the models using methods since the methods can make sure that all required information has been specified and is valid.

For instance, if you can mark an item as completed it's likely that the UpdatedAt date should be changed too. If you let the calling code make sure of that it's likely that it will be forgotten somewhere. Instead you should have something like:

public class MyDomainModel
{
    public void MarkAsCompleted(User completedBy)
    {
        if (completedBy == null) throw new ArgumentNullException("completedBy");
        State = MyState.Completed;
        UpdatedAt = DateTime.Now;
        CompletedAt = DateTime.Now;
        CompletedBy = completedBy;
    }
}

Read my blog post about that approach: http://blog.gauffin.org/2012/06/protect-your-data/

Update

How to make shure that noone changes the "CreatedBy" and "CreatedDate" later on

I usually have two constructors for the models which also fits the DB. one protected one which can be used by my persistance layer and one which requires the mandatory fields. Put the createdby in that constructor and set the createdate in it:

public class YourModel
{
    public YourModel(User createdBy)
    {
        CreatedDate = DateTime.Now;
        CreatedBy = createdby;
    }

    // for persistance
    protected YourModel()
    {}
}

Then have private setters for those fields.

I get a lot of R# warning "Virtual member call in constructor", I've read about it before and it is not supposed to be a good practice.

That's usually not a problem. Read here: Virtual member call in a constructor

like image 184
jgauffin Avatar answered Nov 15 '22 17:11

jgauffin


Is it Ok to use DateTime.Now in the Domain model at all?

It isn't terrible, but the problem is that you will end up having to duplicate code and it will more difficult to achieve consistency.

Where do you put this kind of information using DDD and EF Code First?

You are correct to assert that this type of information doesn't belong in your domain. It is typically called an audit log or trail. There are a few ways to implement auditing with EF. Take a look at AuditDbContext - Entity Framework Auditing Context for instance, or just search around for EF auditing implementations. The idea is that before EF persists changes to an entity, it raises an event which you can listen to and assign the required audit values.

Should User to be set in the domain object or require it in the Business Layer?

It is best to handle this at the infrastructure/repository level with an auditing implementation as stated above. This is the final stop before data is persisted and thus is the perfect place to take care of this.

like image 43
eulerfx Avatar answered Nov 15 '22 19:11

eulerfx