views:

909

answers:

7

The controllers in my ASP.NET MVC web app are starting to get a bit bloated with business logic. The examples on the web all show simple controller actions that simply pull data out of a repository and pass it to the view. But what if you also need to support business logic on top of that?

Say, for instance, an action that fulfills an order also needs to send an e-mail out. Do I stick this in the controller and copy/paste this logic to any other actions that also fulfill orders? My first intuition would be to create a service like OrderFulfillerService that would take care of all this logic and have the controller action call that. However, for simple operations like retrieving a list of users or orders from the database, I would like to interact directly with the repository instead of having that call wrapped by a service.

Is this an acceptable design pattern? Controller actions call services when they need business logic and repositories when they just need data access?

A: 

Your business logic should be encapsulated in business objects - if you have an Order object (and you do, don't you?), and a business rule states that an email should be sent when the Order is fulfilled, then your Fulfill method (or, if more appropriate, the setter for IsFulfilled) should trigger that action. I would probably have configuration information that pointed the business object at an appropriate email service for the application, or more generally to a "notifier" service so that other notification types could be added when/if necessary.

Harper Shelby
+1  A: 

If you are going to have a business layer, then I think that it is best to have only the business layer talk to the data layer. I can understand why in a simple use case, you'd have the presentation layer (the controller) talk to the data layer directly, but once you identify a need for an isolated business layer, then mixing usage of the two in higher levels gets to be dangerous.

For example, what if controller A calls a method in the business layer to fetch a List of Object A (and this method applies the business rules - maybe some filtering or sorting), but then Controller B comes along, needs the same piece of data, but forgets about the business layer and calls the data layer directly ?

matt b
+1  A: 

It might seem annoying to see lot of this in business services:

public Customer GetCustomer(int id)
{
     return customerRepository.Get(id);
}

And it's natural to have a strong urge to bypass the service. But you are better off in the long run allowing your business services intermediate between controllers and repositories.

Now, for a very simple CRUD type application, you could have your controllers consume repositories directly instead of going through business services. You could still have something like an EmailerService, but IMO when it comes to fetching and doing things with entities, best not to mix and match business service and repository calls in your controllers.

As for having entities (business objects) call services or any infrastructure components, I would not do that. I prefer to keep entities POCO and free of dependencies.

Tim Scott
+1  A: 

It would help if we could stop seeing this example over and over again ...

public ActionResult Index()
{
  var widgetContext = new WidgetDataContext();
  var widgets = from w in widgetContext.Widget
                select w;
  return View(widgets);
}

I do realize that this isn't helpful to your question but it seems to be part of a lot of demo-ware that I think can be misleading.

MotoWilliams
Agree. It's "OK" as an example I guess, but it would be nice if they mentioned it's not a best practice from an architecture perspective.
Chris Stewart
+11  A: 

Your controllers (in the MVC project) should be calling your objects in the Service project. The services project is where all the business logic is handled.

A good example is this:

public ActionResult Index()
{
    ProductServices productServices = new ProductServices();

    // top 10 products, for example.
    IList<Product> productList productServices.GetProducts(10); 

    // Set this data into the custom viewdata.
    ViewData.Model = new ProductViewData
                         {
                             ProductList = productList;
                         };

    return View();
}

or with Dependency Injection (my fav)

// Field with the reference to all product services (aka. business logic)
private readonly ProductServices _productServices;

// 'Greedy' constructor, which Dependency Injection auto finds and therefore
// will use.
public ProductController(ProductServices productServices)
{
    _productServices = productServices;
}

public ActionResult Index()
{
    // top 10 products, for example.
    // NOTE: The services instance was automagically created by the DI
    //       so i din't have to worry about it NOT being instansiated.
    IList<Product> productList _productServices.GetProducts(10); 

    // Set this data into the custom viewdata.
    ViewData.Model = new ProductViewData
                         {
                             ProductList = productList;
                         };

    return View();
}

Now .. what's the Service project (or what is ProductServices)? that's a class library with your business logic. For example.

public class ProductServices : IProductServices
{
    private readonly ProductRepository _productRepository;
    public ProductServices(ProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    public IList<Product> GetProducts(int numberOfProducts)
    {
        // GetProducts() and OrderByMostRecent() are custom linq helpers...
        return _productRepository.GetProducts()
            .OrderByMostRecent()
            .Take(numberOfProducts)
            .ToList();
    }
}

but that might be all so hardcore and confusing... so a simple version of the ServiceProduct class could be (but i wouldn't really recommend) ...

public class ProductServices
{
    public IList<Product> GetProducts(int numberOfProducts)
    {
        using (DB db = new Linq2SqlDb() )
        {
            return (from p in db.Products
                    orderby p.DateCreated ascending
                    select p).Take(10).ToList();
        }
    }
}

So there you go. You can see that all the logic is in the Service projects, which means u can reuse that code in other places.

Where did i learn this?

From Rob Conery's MVC StoreFront media and tutorials. Best thing since sliced bread. His tutorials explain (what i did) in good detail with full solution code examples. He uses Dependency Injection which is SOO kewl now that i've seen how he uses it, in MVC.

HTH.

Pure.Krome
Nicely explained
redsquare
cheers mate :) thank Rob Conery/Phil Haack/Scott Hanselman, etc for teach me.
Pure.Krome
Where did this pattern of splitting entities into 2 objects (Entities and Services/Controllers) arise? I've seen it in a couple of (primarily Microsoft-based) places, and I really don't understand it. In fact, I've seen it actually cause problems in an ASP.NET web development project.
Harper Shelby
@ Harper - i think it's a progression from the 3-Tier concept. Above, the service project is where the business logic is (eg. the 2nd Tier). data access is the 3rd tier and the control+views are the first tier. In my projects i have a SERVICES and a PIPELINES project, which are both business logic.
Pure.Krome
@Pure.Krome - I think it confuses me because I 'grew up' in OOP with the basic explanation that objects were data *and* the methods that operate on them. The splitting of items into 'pure data' entities and services seems to violate that methodology.
Harper Shelby
NTier doesn't break OOP principals. Objects can still have state and behavior. Using repository/service patterns enforces SRP. (Object is not responsible for how it is retrieved/persisted.)
ebrown
I'd also suggest reading on the ongoing "repository is dead" discussion between Ayende Rahien and others as there is much interesting stuff to be learned from there.http://www.google.at/search?q=ayende+repository+is+dead
Tigraine
Also how I'm approaching my design. +1
Chris Stewart
Yeah ... i've touched on that post before. Much respect for Ayende, also :) Still, i'm on the fence that I'm still pro-Repository Pattern, right now.
Pure.Krome
+2  A: 

I'm not sure about using services for this.

As I understand it, one of the principles of DDD (which I'm reading up on at the moment) is that the Domain Objects are organised into Aggregates and that when you create an instance of the Aggregate root, it can only directly deal with the objects within the Aggregate (to help maintain a clear sense of responsibility).

Creating the Aggregate should enforce any invariants etc.

Taking an example of a Customer class, the Customer might be the Aggregate root and another class within the Aggregate might be Address.

Now if you want to create a new Customer, you should be able to do this using either the Customer constructor or a factory. Doing this should return an object which is fully functional within the Aggregate boundary (so it can't deal with Products as these are not part of the Aggregate but it can handle Addresses).

The database is a secondary concern and only comes into play for persisting the Aggregate to the database, or retrieving it from the database.

To avoid interfacing with the database directly you can create a Repository interface (as discussed) which given an instance of Customer (which includes a reference to Address) should be able to persist the Aggregate to the database.

The point is that the Repository interface IS part of your domain model/layer (the Implementation of the Repository is not). The other factor is that the repository probably should end up calling the same "create" method as if you were creating a new object (to maintain invariants etc). If you're using a constructor this is simple enough as you will end up calling the constructor when the repository "creates" the object from data anyway.

The application layer can directly communicate with the domain (including the repository interface).

So if you want to create a new instance of an object you can e.g.

Customer customer = new Customer();

If the app needs to retrieve an instance of the customer from the repository, there's no particular reason I can think of for it not to call...

Customer customer = _custRepository.GetById(1)

or...

Customer customer = _custRepository.GetByKey("AlanSmith1")

Ultimately it will end up with an instance of the Customer object which functions within it's own limits and rules just as it would if it created the new Customer object directly.

I think services should be reserved for when the "thing" you are trying to work with just isn't an object. Most rules (constraints etc) can be written as part of the Domain Object itself.

A good example is in the DDD Quickly pdf I'm reading at the moment. In there they have a constraint on a Bookshelf object, whereby you can only add as many books as the shelf can contain.

Calling the AddBook method on the BookShelf object checks that space is available before adding the book to the BookShelf's collection of Book objects. A simple example but the business rule is enforced by the Domain Object itself.

I'm not saying any of the above is correct by the way! I'm trying to get my head around all this at the moment!

+1  A: 

Well, its really up to you, i like to keep the controllers as simple as possible, and to archive this i need to encapsulate the bussines logic in a separate layer, and here is the big thing, mainly you have 2 options, assuming you are using Linq2SQL or Entity Framework:

  • You can use the extender methods and partial class to validate your Models just before Saving the Changes (hooks method, you can see an example of this in Nerdinner sample by Scott Gu).

  • The other way (and my favorite, because i feel more control over the app flow), is to use a totally separate layer for bussines logic like Services Layer (you can see this aprroach in the series of tutorials by Stephen Walther in asp.net/mvc zone).

With this two methods you got DRY and clean up your otherwise messy controllers.

Omar