I am trying to achieve a proof of concept where I write a module (let’s say Forum for the purpose of this discussion) that follows Domain Driven Design guidelines and will have a pluggable repository and the entire module will be pluggable either locally in a web server (local hosted dll) or through WCF services.
Following Domain Driven Design guidelines, I would have a set of business objects that would be written like this:
public class Forum
{
readonly IRepository _forumRepository;
public Forum(IRepository forumRepository)
{
_forumRepository = forumRepository;
}
public string Name { get; set; }
public void Save()
{
_forumRepository.SaveForum(this);
}
}
Let’s say the web application needs to create a new forum.
This is all well and good if the module is hosted locally via a dll file. The web application code would simply use Ninject (or whatever DI) to instantiate the business object and call the Save method.
However, what if the implementers wanted to introduce a service layer in between? Let’s say they want to introduce a WCF Services layer in between the application and the database layer because they want the physical architecture to be Web Server -> App Server -> DB Server. Obviously, the Forum module dll would be hosted by both the web server and the app server. However, the module is no longer usable.
If the web server instantiates the object with the repository injection and passes it across the WCF Service layer, the _forumRepository field will get lost even if I allow for my business objects to be serialized.
The implementor should be able to make the application server choose the repository anyway. So, with this requirement, how would the implementor inject the repository on the application server side after receiving the already instantiated object from the web server? Is there a way to tell WCF Services to inject the repository when it instantiates the object during the deserialization process?
I read the article, How to use Dependency Injection (Ninject) with WCF Services, and reviewed the example solution, but this only demonstrates how to inject the repository directly into the service. It doesn’t appear to solve the problem I am discussing here.
Here is an example of how I foresee the code to be if written correctly. As I stated before, the main problem here is that I don’t know how I can inject the repository into the domain object on the services side.
Application Code (Web App or Windows App):
Forum newForum = new Forum();
//Set some properties on newForum.
IKernel kernel = new StandardKernel(); //Instantiate ninject kernel.
//Yes, I know using the using statement means that IService needs to derive from IDisposable. A local service would just have an empty implementation for IDisposable since there would be nothing to clean up and this would allow different service architectures to be plugged into the application.
using (IForumService forumService = kernel.Get<IForumService>()) //Get concrete implementation bound to IForumService via ninject.
{
//Send forum to service layer for local OR WCF processing (on an app server)
forumService.SaveForum(newForum);
}
Services Code (This is the problem):
public class WCFForumService : IForumService
{
public void SaveForum(Forum forum)
{
//PROBLEM SCENARIO: How do I now inject the repository I want to use since I already have an instance of forum?
//Can I make WCF inject the repository when it is deserializing the forum object before passing it into this method?
forum.Save();
}
}
I would prefer to not force implementers of my forum module into creating DTOs for every domain object if at all possible. This violates the DRY principle. It would be great if I could create my domain objects so they are generic and/or extensible enough to be used as the DTOs themselves with very little effort. This way, I can put business rule validation on the domain objects using Data Annotations and the implementer can use these objects in a Web Forms, MVC3, or Silverlight project without having to re-work all of the validation manually.
EDIT: THE ABOVE IS ENTIRELY THE WRONG APPROACH. EDITING TO DEMONSTRATE A MORE VALID APPROACH.
public class Forum
{
public Forum(string name)
{
Name = name;
}
public string Name { get; set; }
}
public interface IForumRepository : IRepository<Forum>
{
void Add(Forum forum);
Forum this[object id] { get; set; }
}
//Client Code (Called from the WCF service hosting the domain)
public class WCFAppService
{
public void SaveForum(ForumDTO forumInfo)
{
IKernel kernel = StandardKernel();
IForumRepository repository = kernel.Get<IForumRepository>();
Forum forum = repository[forumInfo.ID];
if (forum != null)
{
repository[forumInfo.ID] = forumInfo.CopyTo(forum); //Save the Forum to the db.
}
else
{
repository.Add(ForumFactory.CreateFrom(forumInfo)); //Insert the Forum into the db.
}
}
}
Much of the problem that I had when attempting this prototype was that I was focusing too much on the infrastructure concerns (ie. DI, server architecture, etc) while trying to learn DDD in the process. What I have learned about DDD brings me to the conclusion that, when attempting to figure out how to structure a DDD solution, forget about architecture and technology until you have a good grip on what DDD will do for you. I make this note because in my actual DDD project that I am currently working on, DI seems like an unnecessary complication. That’s how simple DDD can make your code.
By passing your repositories to the web server you are skipping layers. Nothing prevents you from calling save there or manipulate data in a way that is not allowed by your business rules. Furthermore, you don’t want to care about database details on the web server. This is the responsibility of the app server.
There should be a completely different interface between app and db server as between app and web server. The interface of the app server should consist of the actions you want to perform on your web server.
Lets say you have a bank accout system. And want to perform a transaction between two accouts. In this case in your solution you would send the two involved accouts to the webserver and it decreases the balance of the first account by the transfered amount and increases the second one by the same amount. Then it sends the accounts back to the appserver for saving.
But this is wrong. What’s correct is to provide a service on the app server that provides a transaction method that takes the involved account numbers and the amount to be transfered and the complete operation is done on the app server.
In your example you should provide a method
ChangeForumName(int forumId, string newName). Here you can now ensure the rules that the name must fulfill. E.g. not empty.