This is post #5 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.
Well that pre-emptive foray into the UI cost me more time than I expected. Meanwhile my posts.xml
file is getting bigger and bigger and there is still a bunch to do before I can switch to a database. Time to kick off the service layer and start to look at repositories. If you want to follow along, the code as of this post will be tagged 2010/11/28/services-and-repositories
.
My domain model is persistence ignorant. The domain objects have no idea how to retrieve, insert, update or delete themselves or each other from any kind of persistence medium. For that I create a wrapper around my application, an API that defines the operations I will allow against my domain model. This API, or service layer, will know how to persist domain objects -- or at least know of the intermediaries that will (the repositories).
In my experience not all application logic can be covered by the domain model. Occasionally there is a need for more procedural type operations that would otherwise end up in a user interface somewhere. Putting these in the service layer instead ensures they will be performed in the right place, at the right time, only coded once, and callable by any UI or other application.
Life without a service layer can be painful, especially as an application grows and evolves. Using a database connection, context or a repository directly in an ASP page, a Web Forms code behind class or an MVC controller can all result in the same kind of pain. Application logic in the UI.
Here's an example.
Imagine you have a few screens that create object X. Pretty straightforward to code, right? Fast forward to a year later and the business decides it wants action Y to be performed whenever object X is created. Maintenance programmers do their best, but the end result is one screen does it right, creates object X, calls action Y, and even wraps it all in a transaction to make sure both happen or neither; screen two does the same but the transaction is missing; screen three doesn't call action Y at all.
Web Forms code behind classes and MVC controllers give developers the opportunity to refactor that business logic out into a separate class, but that doesn't always happen. MVC controllers have the added advantage of being able to run all three screens from the same action, but that doesn't always happen either, and doesn't necessarily make sense if the screens have differing responsibilities.
Even if the developers were astute and managed to get that application logic into one place, the fact remains that it is still stuck in a UI. The second and third UIs don't have access to that code, and the new web service for the accounting guys down the hall can't get to it either.
It makes sense to keep all application logic in the domain model and service layer and entirely separate from the first UI. Whenever I see a database context or repository being consumed directly in a UI, I think about the application logic that will inevitably accrete around it as the years go on. It's hard not to feel sorry for the maintenance programmers.
Perhaps, but a logical service layer doesn't mean I have to start rolling out web services or anything like that. I only have one UI at this stage, a centrally managed web application, so I can safely let that web application consume the service layer assemblies directly. As soon as the business wants a WPF or Silverlight application, or some web services for integration, I may have to start implementing something like WCF. If I do, however, I hope my web service methods will mostly map one-to-one to my service layer methods and act as simple wrappers (fingers crossed).
For me, this is about flexibility. Teasing the application apart a little bit now could save me a lot of effort later.
Enough of the ranting, time to get this done. Neno just needs a few service methods at this point.
namespace StackingCode.Neno.DomainModel.Services
{
public interface IPostService
{
Post GetPost(int id);
Post GetPostByPublishDateAndSlug(DateTime publishDate, string slug);
IQueryable<Post> GetPosts();
void CreatePost(Post post);
void UpdatePost(Post post);
void DeletePost(Post post);
void CreateComment(Comment comment);
void UpdateComment(Comment comment);
void DeleteComment(Comment comment);
}
public interface IUserService
{
User GetCurrentUser();
User GetUserByOpenIdIdentifier(string indentifier);
void CreateUser(User user);
void UpdateUser(User user);
}
}
NHibernate 3.0 has a much deeper implementation of Linq than the previous version did, so I'm keen to find out if I can offer IQueryable
up to the UI. If I can, that will eliminate a lot of method variations as well as sorting and paging parameters.
I could implement these now, but they won't be able to do much until I start defining my repositories.
Repositories are my link to the persistence medium. Whether it's a relational database (it will be, I'm planning to use NHibernate), a NoSQL database, or something else, I'll go through my repository interface to talk to it. Why use a repository interface? I first set up a repository interface (although I didn't call it that) when I started using NHibernate. I wasn't sure if NHibernate was going to work out -- if it didn't, I wanted to be able to throw away just one piece of my application and not have to rework the entire thing. NHibernate did work out and has ever since, but I've continued to keep a repository interface between my application and NHibernate, threatening to remove it at any time... ala the Dread Pirate Roberts.
He said, "All right, Westley, I've never had a valet. You can try it for tonight. I'll most likely kill you in the morning." Three years he said that. "Good night, Westley. Good work. Sleep well. I'll most likely kill you in the morning." - Westley, The Princess Bride
Of course, there's another advantage to keeping a repository interface these days -- TDD.
My repositories are mostly about CRUD, with a few interesting bits thrown in. To avoid having to repeat the CRUD too much, I'll abstract it out a little. Where are you, Moja?
namespace StackingCode.Moja.DomainModel.Repositories
{
public interface IRepository<TEntity, TEntityId> where TEntity : IEntity<TEntityId>
{
TEntity Get(TEntityId id);
void Create(TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity);
}
}
Now I can specify some repository interfaces back in Neno.
namespace StackingCode.Neno.DomainModel.Repositories
{
public interface ICommentRepository : IRepository<Comment, int>
{
}
public interface IPostRepository : IRepository<Post, int>
{
Post GetByPublishDateAndSlug(DateTime publishDate, string slug);
IQueryable<Post> GetList();
}
public interface IUserRepository : IRepository<User, int>
{
User GetByOpenIdIdentifier(string identifier);
}
}
Services work with repositories, and repositories typically work with just one domain class. It's possible for service methods to have to use several repository methods, and even several repositories. That means transactions! My services will need access to some kind of context object to start transactions on their behalf.
using System.Data;
namespace StackingCode.Moja.Repositories
{
public interface IContext
{
ITransaction BeginTransaction();
ITransaction BeginTransaction(IsolationLevel isolationLevel);
}
public interface ITransaction
{
void Commit();
void Rollback();
}
}
Once I have that, I can abstract out a Service
class for my Neno services to inherit from.
namespace StackingCode.Moja.Services
{
public abstract class Service
{
protected IContext Context { get; set; }
}
}
And now I can finally implement my services, which at this stage are just linking service methods to repository methods.
namespace StackingCode.Neno.Services
{
public class PostService : Service, IPostService
{
protected IPostRepository PostRepository { get; set; }
protected ICommentRepository CommentRepository { get; set; }
#region IPostService Members
// ...
}
public abstract class UserService : IUserService
{
protected IUserRepository UserRepository { get; set; }
#region IUserService Members
public abstract User GetCurrentUser();
public User GetUserByOpenIdIdentifier(string identifier)
{
return UserRepository.GetByOpenIdIdentifier(identifier);
}
// ...
}
}
Well actually I didn't implement all of the UserService
class. The GetCurrentUser
method will depend on something not available here. More on that later.
There are 0 comments.
browse with Pivot
Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue
Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)
Comments
Leave a Comment
Please register or login to leave a comment.