Pragmatic Domain-Driven Design: supporting JSON Patch in Entity Framework Core

Created: September 20, 2018

I’m a big fan of Domain-Driven Design (DDD) and build all my Entity Framework Core (EF Core) entity classes using a DDD approach, i.e. all properties have private setters and all changes are made via method in the entity class. This makes it very clear what, and how, properties and relationships can be updated. (See the article “Creating Domain-Driven Design entity classes with Entity Framework Core” for more details on implementing DDD in EF Core).

But there are times where a fully DDD-styled entity can create more work for yourself. I’m working with a client on a large application that will have hundreds of Web APIs feeding an Angular front-end. In this case using the JSON Patch feature will save the frontend developers a lot of time, because there are libraries that will process a form and create a JSON Patch to update the properties that have changes. (see the article “JSON Patch With ASP.NET Core“ for a fuller description of JSON Patch).

In this article I talk about how I merged the use of JSON Patch approach into my DDD entity class approach. I also describe how I added support for JSON Patch in my EfCore.GenericServices library, while keeping all the good attributes of a DDD-styled entity class.

Introducing the hybrid DDD entity class

JSON Patch, with its update via the property’s setters, seems at odds with the DDD approach to entity classes where properties have private setters. But you can’t ignore the fact that JSON Patch, and associated libraries, can significantly speed up the building of single-page applications, using Angular, React, Vue etc.

DDD is all about making the entity class business-focused, where it deals in terms of the business needs rather than simple properties. So, having methods in the entity class with names like MarkOrderAsDispatched is better than setting the OrderStatus to Dispatched. Also, DDD wants to provide the developer with a clear idea of if/how a property can be updated, especially around relationships.

When building my EfCore.GenericServices library I made it capable of updating “standard” entity classes, i.e. with public setters, and also work with DDD-styled entities, i.e. where updates are done via calling public methods. In all of this I did allow what I call a hybrid DDD entity class, where some properties were updated by method calls, and some were updated via their public setters.

The typical use case for a hybrid DDD entity class is updating an address, with its various street, city, state, zip/postcode properties, inside entity class. I wanted to keep DDD’s tight control over the relationships, but in many cases updating the address properties has no business effects so it simplified things to make the properties’ setters public (the library would notice that and call AutoMapper to update the properties from a DTO).

While some might say that a hybrid DDD entity class breaks the DDD approach, but I believe it follows the spirit of what Eric Evans was getting at in his book, Domain-Driven Design. The ‘intent’ of what can be done with property is clearly communicated.

  • If a property has a public setter then there are no business logic implications (other than validation).
  • If the property has a private setter then it either has business logic implications or you wanted to provide a business-named access method, like my earlier example of MarkOrderAsDispatched. Either way you must update it via a method call because the setter is private.

Altering a DDD-styled entity to allow JSON Patch to work

JSON Patch only works if the properties have public setters – which is where the hybrid DDD entity class comes in.  Here is an example from my EfCore.GenericServices.AspNetCore library, which contains some simple entity classes and Web API to show how things work (you can clone and run this locally to try this out – it uses an in-memory which is seeded at startup to make it easier to play with).

I created a simple hybrid entity class called TodoItemHybrid, as shown below

public class TodoItemHybrid
{
    public int Id { get; private set; }

    [Required(AllowEmptyStrings = false)]
    public string Name { get; private set; }

    [Range(1, 5)]
    public int Difficulty { get; set; }

    //ctor and access methods
    public TodoItemHybrid(string name, int difficulty)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
        Difficulty = difficulty;
    }

    public IStatusGeneric ChangeName(string name)
    {
        var status = new StatusGenericHandler();
        if (name.EndsWith("!"))
            status.AddError("Business logic says the name cannot end with !", nameof(Name));

        Name = name;
        return status;
    }
}

You can see that the Name property (line 6) has a private setter and can only be updated via the ChangeName method (lined 18 to 26). That method has some (fake) business logic connected to it (see lines 21 to 22), so that must be updated via the ChangeName method.

On the other hand, the Difficulty property (line 9) is a simple property with no business logic attached (apart from validation), so I have given it a public setter. This means I can update it via the Microsoft.AspNetCore.JsonPatch library.

The way that JSON Patch works is you can provide a list of changes. In this simple case there is only one property, Difficulty, but you can provide an array of updates. Here is simple example containing two updates to the same class:

[
	{ "op": "replace", "path": "/FirstName", "value": "Jon" }
	{ "op": "replace", "path": "/LastName", "value": "Smith" }
]

Using JSON Patch can cut down the amount of work required in the front-end code. A JavaScript library such as https://www.npmjs.com/package/rfc6902 can automate the creating of the JSON patches to send back to the server. This package has a createPatch feature that compares original data with new data (for instance, from a form) and generate a patch with all the changes to send to the server.

A look at the ASP.NET Core Web APIs

To apply such an update from a Web API controller, then you would need something like this

[HttpPatch("{id}")]
public IActionResult HandCodedUpdate(int id, 
     JsonPatchDocument<TodoItemHybrid> patch, 
     [FromServices]ExampleDbContext context)
{
    var entity = context.Find<TodoItemHybrid>(id);
    if (entity == null)
    {
        return NoContent();
    }
    patch.ApplyTo(entity);
    context.SaveChanges();
    return Ok();
}

The call sends the primary key of the entity you want to update (end of line 2), plus a JsonPatchDocument<T> patch (line 3) created by the caller. Then you have to load the entity you want to update (line 6) and if found you apply the patch and save the changes to the database (lines 11 to 12).

To make this more streamlined I have added a JSON Patch feature to my EfCore.GenericServices library. This wraps the ApplyTo with extra checks, optional validation and success/error messages. Here is the same example but using the GenericServices library:

[HttpPatch("{id}")]
public ActionResult<WebApiMessageOnly> Update(int id, 
    JsonPatchDocument<TodoItemHybrid> patch, 
    [FromServices]ICrudServices service)
{
    service.UpdateAndSave(patch, id);
    return service.Response();
}

Conclusion

DDD can provide many benefits, such as access methods which are named in a business-focused way, and these access methods are the definitive way to do something, as there is no other way in. I really like that level of isolation.

But most database tables have plenty of simple read/write columns that don’t need the level of access control. Writing access methods still works, but sometimes a simpler copy of data is sufficient. And when approaches like JSON Patch make the developers life simpler its worth adopting it in the right places.

In the end DDD is more a philosophy than a standard pattern – Eric Evans was saying “make the domain (business) problem the focus, not the tools and technology you use”. My hybrid DDD entity class might not be liked by some, but I think it still conveys the intent of the data, and it makes you a quicker developer.

Happy coding!