Best practices on data access and the “Unit of Work” pattern in Campus solutions

Cmc.Core is a nuget package provided by Campus Management to address many of the cross-cutting concerns of software design and architecture in our own solutions.

In this blog entry, we will focus on the use of the Unit of Work pattern provided by the Cmc.Core nuget.

The goal of a “Unit of Work” (or UOW) implementation is to keep track of any changes you perform to certain data objects during a business transaction or a single program operation. When you’re done with your changes, the UOW will figure out how to update your target database to match the changes you made to the data objects it was tracking for you. The UOW will then complete those changes on the database as a single operation. If any of the database updates fail to complete successfully, the UOW can “roll-back” all of your database changes thus insuring that your database is not updated with an incomplete operation.

You can read more details about the UOW pattern at:
http://www.martinfowler.com/eaaCatalog/unitOfWork.html

Recommended best practice:

The UOW pattern is typically only used within the Service layer (or business logic layer) of a solution. It is not a good practice to access the database from MVC controllers or from the UI layer of a software solution. By keeping your data access within the service layer (or logic layer) of a solution, you increase the capability to reuse the code and you make the code easier to test.

——

Below we will show an example of a service class that implements the UOW pattern by using the implementation of Cmc.Core.Data.IUnitOfWork found in the Cmc.Core nuget package.

If you have access to the Campus Management TFS server,
you can see a working version of an MVC5 solution that implements this example at:
$/Framework/PoC/MvcAppWithDIusingCmcCore

——

The example service class we will use for demonstration is as follows:

The service will use the ID to retrieve a record from an Entity Framework database.

SayHello() will simply return: “Hello ” + persons’ name.

GetPerson() will return the Person (EF) data object result.

——

The concrete implementation of the service interface

Below is the concrete implementation of this service:

Best practices that are demonstrated in this concrete service implementation:

  • Dependencies are passed in using constructor injection
     
     
    Notice that both dependencies (the ILogger and Func<IDataModel>) are passed in to the constructor. This makes this class easy to unit test this class. When any person attempts to write a unit test for HelloServices, they will notice that they need to inject an instance of these 2 dependencies in order to create an instance of this class.
     
  • A class that implements UOW (IDataModel) is injected as a dependency

    In this example, the IDataModel is a class that implements the unit of work by implementing the IUnitOfWork interface found in Cmc.Core.Data. (see below)

     
  • The IDataModel is passed in as a Func delegate.
     
     
    The use of the attribute [Injectable(InstanceScope.Singleton)] on the HelloServices class means that this service class will be created as a “singleton”.In other words, when the main application process starts, the DI contanier will try to reuse the same instance of the HelloServices anytime that the IHelloServices is injected to any other class a dependency.A service class usually has no state; it is primarily a container of business actions (or methods). It is considered a good practice to reuse the same instance of the HelloService class every time that any class needs to receive a concrete implementation of the IHelloServices interface.A Unit of Work implementation should behave very differently than a service class. A new instance of the UOW should only be created at the start of every new business operation and should be disposed of when the operation completes. (A single business operation may create, update or delete multiple database objects)By injecting a delegate (i.e. Func) instead of an instance of IDataModel to the constructor of HelloServices then we can take advantage of a very cool Autofac feature.

    Autofac will examine the constructor parameters when it attempts to create an instance of HelloServices. When it notices that the HelloServices class requires a delegate of Func<IDataModel>, it will automatically figure out how to create a factory for IDataModel based on the classes that are registered in your DI container.

    Autofac, will build and inject a factory for IDataModel, saving you the bother of having to even define one in the first place. The result is that a new instance of IDataModel will be created every time that the CreateUnitOfWork() method is executed.

    This means that when the using (var unitOfWork = CreateUnitOfWork()){ … } scope is exited, the Service class will dispose of the Unit of Work instance
    – exactly the behavior we want for our UOW implementation! 🙂

 

  • Access to data is via repositories created through the UOW instance
     
     

    The easiest and most effective way to access your data, it to use the repository pattern built into Cmc.Core and provided by the IUnitOfWork.

    Using a Unit Of Work implementation created by the CreateUnitOfWork() method makes it easy to query the records in the database and perform any necessary changes.

    It also provides you with the capabilities to implement transaction tracking and roll back any changes in case any of your changes fails to save successfully to the database.

——

I hope this example allows everyone to better understand the UOW pattern and how we should try to implement it throughout any Campus Management solution.

Please contact me if you have any questions or if you need any further clarifications on how to implement this important architectural pattern in any of our solutions.

Leave a Reply

Skip to toolbar