Click here to Skip to main content
Click here to Skip to main content

Lightweight Entity Services Library

, 5 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This article provides a concrete example of a very simple and lightweight – yet useful – entity services library.

Introduction

If you have more than a few years of experience within domain-driven software development (DDD), most certainly, you have recognized some kind of overall pattern in the type of problems you have to solve – regardless of the type of applications you are working on. I certainly know that I have.

No matter whether you develop desktop applications, web applications or web APIs, you will almost always find yourself in a situation where you have to establish a mechanism for creating, persisting and maintaining state of various entities in the application domain model. So, every time you start up a new project, you have to do a lot of yak shaving to establish this persistence mechanism, when what you really want to do is to work on establishing the domain model – the actual business functionality of your application.

After several iterations through various projects, I have established a practice that works for me in almost any situation. This practice allows you to abstract the entity persistence (the yak shaving…) so that you can easily isolate the nitty-gritty implementation details of this and focus on developing your genuine business functionality. Eventually, of course you have to deal with the implementation of the persistence layer, but the value of being able to develop – and not at least test – your domain model in isolation without having to care about the persistence details is tremendous. Then, you can start out with developing and testing your domain model against fake repositories. Whether you eventually end up making simple file based repositories or decide to go full-blown RDBMS doesn't matter at this point in time.

For this article, I have digested this practice of mine into something I call an Entity Services Library. This library is super lightweight comprising only a few plain vanilla C# classes. No ORM is involved – the repositories can be anything from in-memory objects to RDBMS. No 3rd party dependencies whatsoever.

Background

The technical ingredients of this practice – or library if you wish – are nothing else than some general object oriented software development principles know as SOLID, the repository pattern and, in particular, dependency injection.

There are numerous resources out there describing these principles and patterns. I have made my own attempt in a series of blog posts that can be found here.

Using the Code

Let's dig into the matter. The overall idea is to create well-defined points of interaction – so called seams - in the form of repository interfaces between the domain model services and the persistence layer repositories. Here exemplified by the dependency graph of a product service and its corresponding repository:

The abstraction of a repository is defined in the IRepository and IReadOnlyRepository interfaces.

public interface IRepository<E, T> : IReadOnlyRepository<E, T> where E : IEntity<T>
{
    void Add(E entity);

    void Remove(T id);

    void Update(E entity);
} 

Notice that IRepository inherits the signature of IReadOnlyRepository.

public interface IReadOnlyRepository<E, T> where E : IEntity<T>
{
    int Count { get; }
 
    bool Contains(T id);
 
    E Get(T id);
 
    IQueryable<E> Get(Expression<Func<E, bool>> predicate);
 
    IEnumerable<E> GetAll();
}

Obviously, the repository abstraction could have been kept in a single interface, but due to the Interface segregation principle, it makes sense to split it into (at least) two. You might end up having to implement a simple read-only repository and then you don't want to carry the burden of a too heavy interface. In production code, you might consider splitting the repository interface into even more sub-interfaces.

The abstraction of an entity in the domain model is defined by the generic IEntity interface.

public interface IEntity<T> : INotifyPropertyChanged, INotifyPropertyChanging
{
    T Id { get; set; }
 
    string Name { get; set; }
} 

The generic type T represents the type of the ID of the entity which typically varies depending on the type of entity. You might prefer string IDs for some entities while other types of entities might require for example integer or GUID IDs. Also notice that IEntity inherits the INotifyPropertyChanged and INotifyPropertyChanging interfaces. This is to support possibly data binding which comes in very handy during UI development.

The entity abstraction is brought one step further by the generic abstract BaseEntity class which provides a basic implementation of the IEntity interface.

public abstract class BaseEntity<T> : IEntity<T>
{
    private T id;
    private bool isModified;
    private string name;

    public BaseEntity(T id, string name)
    {
        this.id = id;
        this.name = name;
    }

    ...

} 

The entity service abstractions are provided by two generic abstract base classes called BaseService and BaseReadOnlyService using a repository and a read-only repository respectively. Not surprisingly, the BaseService extends the BaseReadOnlyService. The repository dependency is handled by injecting a repository instance through the constructor. The service does not need to know anything about the concrete implementation of the repository other than the fact that it fulfils the contract as defined by the IRepository interface. This is dependency injection in action.

public abstract class BaseService<E, T> : BaseReadOnlyService<E, T> where E : IEntity<T>
{
    private readonly IRepository<E, T> repository;
 
    protected BaseService(IRepository<E, T> repository)
        : base(repository)
    {
        this.repository = repository;
    }

    ...

} 

The BaseService rasises Adding and Added events when entities are added. The Adding event supports cancelling. Similar events are raised when entities are updated or removed.

To support unit testing, a generic MockRepository is provided. In here, "persistence” is done in an in-memory object – a Dictionary object. This is obviously useless in a real application but perfect as a fake (mock) repository for unit testing.

public class MockRepository<E, T> : IRepository<E, T> where E : IEntity<T>
{
    private readonly Dictionary<T, E> entities;

    public MockRepository()
    {
        this.entities = new Dictionary<T, E>();
    }

    ...

} 

Using the Entity Services Library

Now let's look at an example of usage of the entity services library. Let's add support for product management. First, you will create a Product class by deriving from the generic abstract BaseEntity class. The generic type parameter defining the type of the Id-property is set to Guid. In addition to the Id- and Name property defined in IEntity, it seems reasonable to add at least a Price property to the Product class. For simplicity, the data binding support is ignored for the Price property:

public class Product : BaseEntity<Guid>
{
    public Product(string name)
        : base(Guid.NewGuid(), name)
    {
    }

    public decimal Price { get; set; }
}

Now, let's say that you want to extend the product repository with a method to detect whether the repository already contains a product with the same name. Then, you can just create an IProductRepository interface by extending the IRepository interface:

public interface IProductRepository : IRepository<Product, Guid>
{
    bool ContainsName(string name);
}

Finally, it is time to create the product service itself. This is basically done by deriving from the generic abstract BaseService class. Since all the methods in the BaseService class are declared as virtual, you can decide to override them - for example to add further constraints when adding a product. The BaseService already throws an exception if you try to add an entity with an already existing ID, but in the below implementation, exceptions are also thrown if a product with the given name already exists or if the product name is empty or undefined.

public class Products : BaseService<Product, Guid>
{
    private readonly IProductRepository repository;
 
    public Products(IProductRepository repository)
        : base(repository)
    {
        this.repository = repository;
    }
 
    public override void Add(Product product)
    {
        if (this.repository.ContainsName(product.Name))
        {
            throw new ArgumentException(string.Format("There is already a product with the name '{0}'.",
            product.Name), "product");
        }
 
        if (product.Name == null || product.Name.Equals(string.Empty))
        {
            throw new ArgumentException("Product name cannot be null or empty string.", "product");
        }
 
        base.Add(product);
    }
}

In real production code, you would probably now extend the product service with additional functionality – for example for calculating discounted prices, currency management, etc.

Needless to say, the pattern for establishing similar services for other entities such as for example users, customers, campaigns, etc. is exactly the same.

Now, let's write some test code. Because you made the IProductRepository as an extension of the IRepository interface, first you need to create a MockProductRepository class as an extension of the MockRepository class. The ContainsName() method must be implemented – at least if you want to test functionality depending on it.

internal class MockProductRepository : MockRepository<Product, Guid>, IProductRepository
{
    public bool ContainsName(string name)
    {
        return this.Entities.Values.Any(p => p.Name.Equals(name));
    }
}

Now, you can test for example that the expected exception is thrown if trying to add a product with an already existing name. Notice how the MockProductRepository is injected into the Products service using constructor injection:

[Fact]
public void AddWithExistingNameThrows()
{
    // Setup fixture
    var products = new Products(new MockProductRepository());
    var product = new Product("MyProduct name");
    products.Add(product);
    var productWithSameName = new Product(product.Name);
 
    // Exercise system and verify outcome
    Assert.Throws<ArgumentException>(() => products.Add(productWithSameName));
}  

And here is a test verifying that the Deleting and Deleted events are properly raised when deleting a product:

[Fact]
public void EventsAreRaisedOnRemove()
{
    // Setup fixture
    var raisedEvents = new List<string>();
    var products = new Products(new MockProductRepository());
    products.Deleting += (s, e) => { raisedEvents.Add("Deleting"); };
    products.Deleted += (s, e) => { raisedEvents.Add("Deleted"); };
    var product = new Product("MyProduct name");
    products.Add(product);
 
    // Exercise system
    products.Remove(product.Id);
 
    // Verify outcome
    Assert.Equal("Deleting", raisedEvents[0]);
    Assert.Equal("Deleted", raisedEvents[1]);
} 

Finally, here is a test proving that the query mechanism using lambda expressions works as intended:

[Fact]
public void GetQueryableIsOk()
{
    // Setup fixture
    var products = new Products(new MockProductRepository());
    var coke = new Product("Coke");
    coke.Price = 9.95M;
    var cokeLight = new Product("Coke Light");
    cokeLight.Price = 10.95M;
    var fanta = new Product("Fanta");
    fanta.Price = 8.95M;
    products.Add(coke);
    products.Add(cokeLight);
    products.Add(fanta);
 
    // Exercise system
    var cheapest = products.Get(p => p.Price < 10M).ToList();
    var cokes = products.Get(p => p.Name.Contains("Coke")).ToList();
 
    // Verify outcome
    Assert.Equal(2, cheapest.Count());
    Assert.Equal(2, cokes.Count());
} 

Many more tests are available in the sample code.

Summary

This article provides a concrete example of a very simple and lightweight – yet useful – entity services library. The ingredients are some general object oriented software development principles as well as the repository pattern and dependency injection. The library consists of plain vanilla C# classes only.

The loose coupling achieved by the use of dependency injection makes it very easy to establish unit tests of domain functionality using mock repository objects. A generic MockRepository class is provided for this purpose.

Practical Information

The tests are written using xUnit.NET which is a personal favourite of mine. XUnit.NET is available through the NuGet Package Manager in Visual Studio. To run the tests, you will have to install for example the xUnit.NET runner. In production code, you should seriously consider using supplementary unit test frameworks such as Moq and Autofixture to help you streamline mocking and fixture setup. Both are available through NuGet.

Production code would obviously also require concrete implementation of the various repositories. In the sample code, I added a simple JsonProductRepository for product persistence in a JSON file. This repository uses the Json.NET library which is available through NuGet. I did not provide unit tests for this repository.

As the entity services library is leveraging dependency injection, it is ideal for usage with a dependency injection container such as for example NInject or Unity – both available through NuGet.

The sample code is made in Visual Studio 2012 using .NET 4.5, but the code compiles against .NET 4.0 and .NET 3.5 also.

In the sample code, the 3rd party dependencies (xUnit.NET and Json.NET) are included, so you don't necessarily need to have NuGet installed to compile and run the code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

lars.michael.dk
Architect
Denmark Denmark
I am a software architect/developer/programmer.
 
I have a rather pragmatic approach towards programming, but I have realized that it takes a lot of discipline to be agile. I try to practice good craftsmanship and making it work.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionNice article but .... PinmemberRahman Mahmoodi28-Feb-14 14:14 
AnswerRe: Nice article but .... [modified] Pinmemberlars.michael.dk1-Mar-14 7:43 
AnswerRe: Nice article but .... Pinmemberlars.michael.dk3-Mar-14 10:59 
GeneralRe: Nice article but .... Pinmemberkrepak3-Mar-14 11:57 
GeneralRe: Nice article but .... Pinmemberlars.michael.dk3-Mar-14 22:41 
GeneralRe: Nice article but .... PinmemberRahman Mahmoodi3-Mar-14 21:12 
GeneralMy vote of 5 PinmemberMember 1050472121-Feb-14 22:26 
GeneralLooks great! PinmemberQuentin in SA19-Feb-14 19:59 
AnswerRe: Looks great! Pinmemberlars.michael.dk19-Feb-14 22:26 
Questionaren't you PinmvpSacha Barber19-Feb-14 7:14 
AnswerRe: aren't you Pinmemberlars.michael.dk19-Feb-14 10:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 5 Aug 2014
Article Copyright 2014 by lars.michael.dk
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid