Single Responsibility Principle. Each of your classes should have one and only one reason to change. @Zirak gives a good example of how each person has a single reponsibility in the chain of events.
Let's look at the hypothetical test case you have provided.
public ActionResult Kittens() // some parameters might be here
{
using(var db = new KittenEntities()){ // db can also be injected,
var result = db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
return Json(result,JsonRequestBehavior.AllowGet);
}
}
With a service layer in between, it might look something like this.
public ActionResult Kittens() // some parameters might be here
{
using(var service = new KittenService())
{
var result = service.GetFluffyKittens();
return Json(result,JsonRequestBehavior.AllowGet);
}
}
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
using(var db = new KittenEntities()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
}
}
With a few more imaginary controller classes, you can see how this would be much easier to reuse. That's great! We have code reuse, but there's even more benefit. Lets say for example, our Kitten website is taking off like crazy, everyone wants to look at fluffy kittens, so we need to partition our database (shard). The constructor for all our db calls needs to be injected with the connection to the proper database. With our controller based EF code, we would have to change the controllers because of a DATABASE issue.
Clearly that means that our controllers are now dependant upon database concerns. They now have too many reasons to change, which can potentially lead to accidental bugs in the code and needing to retest code that is unrelated to that change.
With a service, we could do the following, while the controllers are protected from that change.
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
}
protected KittenEntities GetDbContextForFuffyKittens(){
// ... code to determine the least used shard and get connection string ...
var connectionString = GetShardThatIsntBusy();
return new KittensEntities(connectionString);
}
}
The key here is to isolate changes from reaching other parts of your code. You should be testing anything that is affected by a change in code, so you want to isolate changes from one another. This has the side effect of keeping your code DRY, so you end up with more flexible and reusable classes and services.
Separating the classes also allows you to centralize behavior that would have either been difficult or repetitive before. Think about logging errors in your data access. In the first method, you would need logging everywhere. With a layer in between you can easily insert some logging logic.
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
Func<IEnumerable<Kitten>> func = () => {
using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
};
return this.Execute(func);
}
protected KittenEntities GetDbContextForFuffyKittens(){
// ... code to determine the least used shard and get connection string ...
var connectionString = GetShardThatIsntBusy();
return new KittensEntities(connectionString);
}
protected T Execute(Func<T> func){
try
{
return func();
}
catch(Exception ex){
Logging.Log(ex);
throw ex;
}
}
}