Domain model purity and the current time

Time as an ambient context

public static class DateTimeServer
{
private static Func<DateTime> _func;
public static DateTime Now => _func();
public static void Init(Func<DateTime> func)
{
_func = func;
}
}
// Initialization code for production
DateTimeServer.Init(() => DateTime.Now);
// Initialization code for unit tests
DateTimeServer.Init(() => new DateTime(2020, 1, 1));
  • This implementation pollutes the production code: it introduces production code that’s only needed for testing. Mixing up test and production code increases the maintenance costs of the latter.
  • The static field becomes a shared dependency, thus transitioning the tests using it into the sphere of integration testing because they can no longer run in parallel.
  • The Ambient Context pattern in general and this implementation in particular masks potential problems in code. If injecting a dependency explicitly becomes so inconvenient that you have to resort to ambient context, that’s a certain sign of trouble. Most likely, you have too many layers of indirection.

Time as an explicit dependency

public interface IDateTimeServer
{
DateTime Now { get; }
}

public class DateTimeServer : IDateTimeServer
{
public DateTime Now => DateTime.Now;
}

public class MyController
{
private readonly IDateTimeServer _dateTimeServer;

public MyController(
IDateTimeServer dateTimeServer) // Injecting time as a service
{
_dateTimeServer = dateTimeServer;
}

public void MyAction(int id)
{
MyDomainClass entity = GetById(id);
entity.DoAction(_dateTimeServer.Now); // Injecting time as a plain value
Save(entity);
}
}

A more complex example

public class ServiceAgreementEntity
{
public DateTime StartDate { get; }
public DateTime EndDate { get; private set; }
public ServiceAgreementEntity(DateTime startDate)
{
if (startDate < DateTime.Today)
throw new ArgumentException();
StartDate = startDate;
}
public void SetEndDate(DateTime endDate)
{
if (StartDate > DateTime.Today.AddDays(-5))
throw new ArgumentException();
EndDate = endDate;
}
}
  • On the one hand, you can’t set a past date to the StartDate property
  • But on the other, a past StartDate is required in order to test the SetEndDate method
public class ServiceAgreementEntity
{
public DateTime StartDate { get; }
public DateTime EndDate { get; private set; }
public ServiceAgreementEntity(DateTime startDate, DateTime now)
{
if (startDate < now.Date)
throw new ArgumentException();
StartDate = startDate;
}
public void SetEndDate(DateTime endDate, DateTime now)
{
if (StartDate > now.Date.AddDays(-5))
throw new ArgumentException();
EndDate = endDate;
}
}

Summary

  • Representing the current time as an ambient context pollutes the production code and makes testing more difficult.
  • Inject time as an explicit dependency — either as a service or as a plain value.
  • Inject it as a service into controllers.
  • Inject it as a plain value in domain classes.

Subscribe

--

--

--

Author of https://amzn.to/2QXS2ch. Founder of https://enterprisecraftsmanship.com/

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Essential things You need to know during App Store Optimization Audit

Good Parenting is Scrum Parenting

Should you try all the popular programming languages and frameworks?

Programming languages and frameworks

MY EXPERIENCE AT KWOC 2020

C++: Complete Developer Guide — Part 1

C++ Fast development reference khizaruddins, khizaruddins.medium.com mother of programming language

Read/Write CSV in flutter web

yeah lets go

SURGERY FROM STARK UPDATING : DEFINITION _ VOLUME ONE — PART. SIXTEEN

TextField Date Picker Widget for Flutter

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Vladimir Khorikov

Vladimir Khorikov

Author of https://amzn.to/2QXS2ch. Founder of https://enterprisecraftsmanship.com/

More from Medium

Mediator Pattern: A Capable Underdog

Collections and Primitive Obsession

Anemic Models vs Rich Models in Domain-Driven Design

CQRS & Event Sourcing I : Introduction