Implementing an architecture with ASP.NET MVC (Part 3) – The business layer

Introduction

The focus of this post it to describe how I go about developing the business layer. This post follows on from my previous post  ASP.NET MVC – Creating an application with a defined architecture. In my previous post, I was fulfilling a requirement to fetch a list of customers and display them on a page with ASP.NET MVC. So I will continue on with that as an example.

The Plan

  • At the end of the previous post, i had an object called “CustomerAgent” that just created two instances of the “customer” object. This is going to be replaced with a call the business layer to fetch a list of customers. The business layer will be returning the customers as a message type. The CustomerAgent will map the message type to the customer object that is already defined in the “PresentationProcesses” assembly. We will drive this out with a test.
  • In the business layer, we will need to respond to the call for fetching a list of customers. Our business layer will ask a “repository” to fetch customers from a datastore. The business layer will take a list of customers and map them into a message that will be returned to the caller.
  • In the next post to continue on from this one, the repository will need to get the customers from somewhere and map them into instances of objects that represent a customer in the domain model. An ORM tool will simplify this process.

While implementing this plan, we will be testing the interactions between layers. We will also be registering the more types in our IOC container.

Putting the plan into action

The CustomerAgent is not under test and currently returns fake instances, so we will create a new test assembly and place the “customer agent” under test and start driving out the interaction with the business layer.

  1. Create a new class library in your solution called “Web.PresentationProcesses.Specs”.
  2. Add a project reference to the “Web.PresentationProcesses” project.
  3. Add file references to “Nunit.framework”, “Rhino.Mocks” and “NSpec” (or whatever unit testing framework and mocking tool use want to use).
  4. Add a new class (test fixture) to this new assembly called “Fetching_a_list_of_customers” and decorate it with the “[TestFixture]” attribute.
  5. Add a test called “Should_fetch_and_return_customers”. This test will return a list of “customers” and assert that the result was not null. At this point the test will pass as the “customer agent” is still just returning two made up instances. Here is the test (its not the final test, its going to change)
    [TestFixture]
    public class Fetching_a_list_of_customers
    {
        private ICustomerAgent agent;
    
        [SetUp]
        public void SetUp()
        {
            agent = new CustomerAgent();
        }
    
        [Test]
        public void Should_fetch_and_return_customers()
        {
            List<Customer> customers = agent.GetCustomerList();
    
            customers.ShouldNotBeNull();
        }
    }
  6. Currently the “Customer Agent” is a public class, i don’t want my implementations to be public. The interface will be only way that the above layer can communicate with this. But we will still want our tests to be able to work with the concrete implementation and also so will our mocking tool. In the “Web.PresentationProcesses” assembly, open up “AssemblyInfo.cs”. Add the following lines and save and close the file.
    [assembly: InternalsVisibleTo("Web.PresentationProcesses.Specs")]
    
    [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
  7. Next step, change the “CustomerAgent” class to be “internal” instead of “public”. Run our tests to verify that it all still works.

Driving out the business layer

As mentioned earlier the service is going to return data as a message type. This is going to use a very common message pattern being “Request/Response” which is also known as the “request/reply” pattern.

The business layer is going to be in its own assembly, it will run in-process to the MVC application. We could in the future without to much effort, place the business layer behind a WCF endpoint and host the business layer in another process. I personally would not to this by default, the reasons for making the business layer remote must be because you either have more than one application that interacts with the business layer tier or because of scalability. Scalability over performance its down to your applications needs, availability and size of the user base. Moving the business layer to run out of process is another blog post that i will write as the final post of this series as optional approach. Although the its not that different.

At the moment we have no business layer, so from the test i above, we start defining the interface (contract) in the business layer.

I am a big fan of Resharper, it makes my world a better place. It saddens me to think that developers are out their coding without the fruits that resharper gives.

  1. Back into the unit test, cutting a not such a long story to be a shorter story. I am going to set up an expectation on an interface and return a response object. I have also driven out the properties that are in the response object. The test is also asserting that the “customer” UI object is populated with the values from the response  Here is the test fixture. I have created the new types within the same code file as the fixture. I do this sometimes while i am cutting new code and creating new types, then i will (“with the help of resharper”) move the classes into there own files and move them into the correct assemblies. Which i do as the next step.
    using System.Collections.Generic;
    using NBehave.Spec.NUnit;
    using NUnit.Framework;
    using Rhino.Mocks;
    using Web.PresentationProcesses.Customers;
    
    namespace Web.PresentationProcesses.Specs
    {
        [TestFixture]
        public class Fetching_a_list_of_customers
        {
            private ICustomerAgent agent;
            private ICustomerService service;
    
            [SetUp]
            public void SetUp()
            {
                service = MockRepository.GenerateMock<ICustomerService>();
    
                agent = new CustomerAgent();
            }
    
            [Test]
            public void Should_fetch_and_return_customers()
            {
                FetchCustomerResponse response = GetResponse();
    
                service.Expect(x => x.FetchCustomers()).Return(response);
    
                List<Customer> customers = agent.GetCustomerList();
    
                customers.ShouldNotBeNull();
                customers.Count.ShouldEqual(response.Customers.Count);
                IsMappingCorrect(response.Customers[0], customers[0]).ShouldBeTrue();
    
                service.AssertWasCalled(x => x.FetchCustomers());
            }
    
            private FetchCustomerResponse GetResponse()
            {
                return new FetchCustomerResponse
                {
                    Customers = new List<CustomerInfo>
                    {
                        new CustomerInfo
                        {
                            AccountManagerName = "Mr Account Manager",
                            AccountNumber = "ABC 123",
                            City = "Some Town",
                            Country = "Some Country",
                            Name = "Happy Customer"
                        }
                    }
                };
            }
    
            private bool IsMappingCorrect(CustomerInfo customerInfo, Customer customer)
            {
                return customerInfo.AccountManagerName == customer.AccountManagerName &&
                       customerInfo.AccountNumber == customer.AccountNumber &&
                       customerInfo.City == customer.City &&
                       customerInfo.Country == customer.Country &&
                       customerInfo.Name == customer.Name;
            }
        }
    
        public class FetchCustomerResponse
        {
            public List<CustomerInfo> Customers { get; set; }
        }
    
        public class CustomerInfo
        {
            public string Name { get; set; }
            public string AccountNumber { get; set; }
            public string AccountManagerName { get; set; }
            public string City { get; set; }
            public string Country { get; set; }
        }
    
        public interface ICustomerService
        {
            FetchCustomerResponse FetchCustomers();
        }
    }
  2. At the moment, the code will compile, but the test will fail. As mentioned in the last step, I am going to move the “FetchCustomerResponse”, “CustomerInfo” and “ICustomerService”  into another assembly.
    1. Add a new class library to the solution called “Web.Business”
    2. Create a folder called “Contracts” and move the “ICustomerService” into this folder and change the namespace to match it new location.
    3. In the Contracts folder, add a new folder called “Messages”. Move the CustomerInfo and FetchCustomerResponse types into this new folder and change the namespaces.
  3. In both the “Web.PresentationProcesses” and “Web.PresentationProcesses.Specs”, add a project reference to “Web.Business”.
  4. The CustomerAgent needs to be able to talk to the “ICustomerService”, change the constructor of the “CustomerAgent” to be passed a reference of “ICustomerService” and hold the reference in a variable in the “CustomerAgent”. The “SetUp” on the unit test will change to pass in the service into the constructor of the “CustomerAgent”.
  5. Now to make the test pass, the code in the method ”GetCustomerList” in the “CustomerAgent” has been replaced to make the test pass. Here is that code for the modified “CustomerAgent” as well as the changes to the SetUp method in the test.
    // Unit test
    
    [SetUp]
    public void SetUp()
    {
        service = MockRepository.GenerateMock();
    
        agent = new CustomerAgent(service);
    }
    
    // Customer agent
    
    using System.Collections.Generic;
    using System.Linq;
    using Web.Business.Contracts;
    using Web.Business.Contracts.Messages;
    
    namespace Web.PresentationProcesses.Customers
    {
        internal class CustomerAgent : ICustomerAgent
        {
            private readonly ICustomerService service;
    
            public CustomerAgent(ICustomerService service)
            {
                this.service = service;
            }
    
            public List<Customer> GetCustomerList()
            {
                List<Customer> result = new List<Customer>();
    
                FetchCustomerResponse response = service.FetchCustomers();
    
                result.AddRange((from custInfo in response.Customers
                    select new Customer
                    {
                        AccountManagerName = custInfo.AccountManagerName,
                        AccountNumber = custInfo.AccountNumber,
                        City = custInfo.City,
                        Country = custInfo.Country,
                        Name = custInfo.Name
                    }).ToList());
    
                return result;
            }
        }
    }
  6. All the tests, now pass. Now to create a concrete “CustomerService”. Create a new folder in the “Web.Business” assembly called “Customers” and add a new internal class called “CustomerService”.
  7. Make the “CustomerService” implement the “ICustomerService” interface. We will come back to the method “FetchCustomers” later, so just throw a NotImplemented exception for minute.
  8. We need to register the types in the IOC container. We need to pass the IOC container to the “Web.Business” assembly so that it can register its types.
    1. Add a reference to Unity or what ever IOC container that you are using.
    2. Create a public class called “BusinessModule” and add a public method below.
      public class BusinessModule
      {
          public void Configure(IUnityContainer container)
          {
              container.RegisterType<ICustomerService, CustomerService>();
          }
      }
    3. In the “Web.PresentationProcesses” assembly, add a reference to the “Web.Business” assembly.
    4. In the “PresentationProcessesModule”, in the configure method, create a new instance of the “BusinessModule” and call the “Configure” method passing in the container.

The Customer Service

Now to implement the CustomerService. The service itself is just a facade and brings its internals together to provide a simple API. The service will delegate to objects that have the responsibility to carry out required actions. In the case of the CustomerService, it will ask a repository to return a list of customers. The customers will be instances of a domain entity called “Customer”. The service will map the domain type to the message type.

I keep the domain isolated from the outside world. The only way to interact with the domain from the outside world is through a service. The service does not contain much logic, if it did it would mean that logic is not in the domain and the domain would be not be rich. A thin domain and rich services would the “anemic domain model” anti-pattern. In this example, I am pulling data out of a repository, so their is no business logic.

  1. Firstly, create a new class library assembly called “Web.Business.Specs” which you may have guessed is going to hold the tests for the business assembly. Add references to NUnit and Moq/RhinoMocks or what ever is your preferred mocking tool.
  2. We are going to be testing internal objects within the web.business project. As described earlier on in this post. Add this two lines to the assemblyInfo.cs in the “web.business” project.
    [assembly: InternalsVisibleTo("Web.Business.Specs")]
    [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
  3. Create a new test fixture called “Fetching_customers”. Our is going to ask the service to provide a list of customers. This information will be provided a list of “CustomerInfo” objects contained in the “FetchCustomerResponse”. Here is the test.
    using System.Collections.Generic;
    using NBehave.Spec.NUnit;
    using NUnit.Framework;
    using Rhino.Mocks;
    using Web.Business.Contracts;
    using Web.Business.Contracts.Messages;
    using Web.Business.Domain;
    using Web.Business.Persistence;
    using Web.Business.Services;
    
    namespace Web.Business.Specs
    {
        [TestFixture]
        public class Fetching_customers
        {
            private ICustomerService customerService;
            private ICustomerRepository customerRepository;
    
            [SetUp]
            public void SetUp()
            {
                customerRepository = MockRepository.GenerateMock<ICustomerRepository>();
    
                customerService = new CustomerService(customerRepository);
            }
    
            [Test]
            public void Should_return_a_list_containing_customer_information()
            {
                List<Customer> customers = new List<Customer>
                {
                    new Customer
                    {
                        AccountManagerName = "Mr A Manager",
                        AccountNumber = "ABC 123",
                        City = "Some Place",
                        Country = "Some Island",
                        Name = "Some Customer"
                    }
                };
    
                customerRepository.Expect(x => x.FindAll()).Return(customers);
    
                FetchCustomerResponse response = customerService.FetchCustomers();
    
                customerRepository.AssertWasCalled(x => x.FindAll());
    
                response.Customers.Count.ShouldEqual(customers.Count);
    
                IsMappingCorrect(response.Customers[0], customers[0]).ShouldBeTrue();
            }
    
            private bool IsMappingCorrect(CustomerInfo customerInfo, Customer customer)
            {
                return customerInfo.AccountManagerName == customer.AccountManagerName &&
                       customerInfo.AccountNumber == customer.AccountNumber &&
                       customerInfo.City == customer.City &&
                       customerInfo.Country == customer.Country &&
                       customerInfo.Name == customer.Name;
            }
        }
    }
  4. The above test drove out the customer Repository interface and a domain object called customer. At the moment i have added two new folders “Persistence” and “Domain” to the “Web.Business” project and place the customer object in the domain folder and repository interface in the “Persistence” folder. Getting a step ahead, i have created a concrete implementation of CustomerRepository. Here is the code for the new types:
    namespace Web.Business.Domain
    {
        internal class Customer
        {
            public string Name { get; set; }
            public string AccountNumber { get; set; }
            public string AccountManagerName { get; set; }
            public string City { get; set; }
            public string Country { get; set; }
        }
    }
    
    //
    
    using System.Collections.Generic;
    using Web.Business.Domain;
    
    namespace Web.Business.Persistence
    {
        internal interface ICustomerRepository
        {
            List<Customer> FindAll();
        }
    }
    
    //
    
    using System.Collections.Generic;
    using Web.Business.Domain;
    
    namespace Web.Business.Persistence
    {
        internal class CustomerRepository : ICustomerRepository
        {
            public List<Customer> FindAll()
            {
                return new List<Customer>();
            }
        }
    }
  5. Lastly,  wire up the repository in IOC container, the configure method in “BusinessModule” class should look like this.
    public void Configure(IUnityContainer container)
    {
        container.RegisterType<ICustomerService , CustomerService>();
        container.RegisterType<ICustomerRepository , CustomerRepository>();
    }

That’s it for this post. the next step is to use an ORM to fetch the data from the database, which will be the responsibility of the repository.

Advertisements