OK, so a few days ago, we at Elegant Code hosted an Open Spaces dinner. A question came up about what architecture we use for various projects — specifically Asp.Net WebForms. This sort of thing has been published by others, but I thought I would throw mine out there. Also note: this is where I am right now. This has changed a lot over the years, and will change again in the future.
Here is a basic outline. First you have your project name. I’ll call mine: Project51. So I set up the following projects:
- Project51.Web
- Project51.Domain
- Project51.Repository
- Project51.Presenter or Project51.Service
- Project51.Common
- Project51.Tests
Project51.Web is a Web Solution Project. None of that Web Site junk, thank-you-very-much. I’ve been forced to work with Web Site projects far too many times, I don’t like them. Anyway, more on this project later.
Project51.Domain will contain the classes used to host data from the database. These could be LinqToSQL classes, NHibernate domain classes, SubSonic, or just custom classes.
Project51.Repository will contain my classes for talking to the database. If I’m dealing with LinqToSQL, then my LinqToSql queries go here. Same for HQL or SubSonic expressions. Also, if I’m dealing with NHibernate, this is where I put my mapping files. And this is the ONLY layer that talks to the database.
As for what the classes here look like: typically I pick an aggregate root (top node that everything else stems from) or a specific task and name them that. So I could have a SecretProjectsRepository, which would have a SaveSecretProject(SecredProject project) method. This method would then take care of saving any of the data underneath it as well (like alien bodies and UFOs)
Next is Project51.Presenter and/or Project51.Service. I go back and forth on this one. Depending on the size and complexity of your project you may need both, or just one of these projects. But, I keep with the idea that you should probably have more presenter classes than you have web forms. Something like WebForm.Count <= Presenter.Count. But a presenter is designed to talk to a view (through an interface) to perform a specific task. I remember talking to Scott Cate, and he actually has separate presenters for saving and loading specific data. That would have saved me a bunch of trouble if I had done this. I’ll show what a presenter looks like below.
Next a word of warning. A trap that I got into a while back was to use primitives to get data from the view to my presenter. If I had a Name text box, sure enough, my view had a Name property. If I had a Birthday text box on my form, then I would have a corresponding Birthday property. Don’t do that. It makes your interfaces huge and chatty. This hurts testing and maintainability in very bad ways. Now, when sending information between my presenters and views, I use objects.
But what goes in Project51.Service? In a typical application there are a whole host of things that really don’t belong in the presenter, they are just general needs. Typically for logic that gets passed around. That is what I put in services. For a simple project, sometimes I’ll forgo the Presenter and just have a bunch of services. Please note on this one: I’m still reading Domain Driven Design, this definition is bound to change.
Project51.Common mainly contains interfaces. Interfaces for the repositories, services, presenters, and views. Lately I’ve also been putting some extension methods here as well.
Finally is Project51.Web. I’m bringing this one up again, because this is where I hook everything up. Specifically, this is where I initialize my IOC. You can use Unity, StructureMap, Spring.Net, or Castle Windsor for this (there are others, but I haven’t used them). I add all of my Services and Repositories to the IOC using various methods. The two layers that my IOC does not know about are my views (the web pages) and the presenters.
The final bit is some code. How to hook the first few layers together.
First the Page:
public class Main: Page, IMainView
{
private MainPresenter _presenter;
public void Page_Init(...)
{
_presenter = new MainPresenter(this);
}
public void Page_Load(...)
{
if (!Page.IsPostback())
{
_presenter.Load();
}
}
}
Next, my starter Presenter will look like this:
public class MainPresenter
{
private IMainView _view;
private IRepository _dbo;
public MainPresenter(IMainView view)this(view, MyIoc.GetObject<IRepository>())
{
}
public MainPresenter(IMainView view, IRepository dbo)
{
_view = view;
_dbo = dbo;
}
public void Load()
{
// get data from the repository/services
// load the view witht the data
}
}
The only real “tricks” that I’m using here are constructor chaining in the presenter (the first constructor is calling the second presenter), and I added a Page_Init handler (most pages have a Page_Load, but not a Page_Init).
Is this the best way to do things? Probably not. In fact, please tell me where I could do things better.
Also not mentioned is how to test this…but that requires mock objects, and I really don’t want to get into that right now. OK.
Looks like a ol’ big data-driven turd.
Hey Chris,
My variation on the solution setup would be to combine any interfaces defined in Repository, Common, and Service/Presenter into my Domain project, and combine all of the implementations from those three projects into a Core project. I still maintain the same segregation you have within a single assembly using namespaces. A few less projects to compile and deploy.
-Bill
@Markus, Intriguing comment. Care to share what your code would look like so we can have something to discuss?
@Bill, I’m with you for smaller projects. In those cases I break things up with folders. It is just important that you have some separation of your layers.
I have had trouble when working on larger projects in group situations. Trying to keep things separate, but in the same project, can be problematic.
But I do like the idea of putting the interfaces in with the domain objects.
@Chris & Bill: I prefer to start simple, with the same separation via namespaces but in as few assemblies as I can get away with, typically Web, Core, and Test. If the project grows to the point that I need more separation, then I can split the assembly up later – otherwise, YAGNI.
The “Default” architecture should be as simple as possible, but no simpler.
So you keep all these projects in different solutions or in one soultion? Do you recommend against placing all the projects in one solution?
@Milton: I keep them all in one solution. Unless the project get enormous, I favor keeping everything together. But I have seen solutions with 20 projects in them, and sometime I favor breaking them out into separate solutions.
@trasa: I like simplicity as well. But how much more complicated is a adding a folder vs adding another project?
Plus, that does lead into one of my OCD tendencies: I like me namespaces to match up correctly. ReSharper helps with that, but now I have to open each file and change the namespace for the new location. I know, because I just had to do this.
How much more complicated – one is a folder, the other is an entire new project and assembly that you have to care for, set the build configuration, deploy, possibly manage .config files, and so on. Big difference.
And this isn’t so much directed at you, Chris – but I’ve noticed hearing this a lot from many different people – when did using Obsessive Compulsive Disorder become a way to “rationalize” behaviors? OCD tendencies by their definition are irrational.
Me claiming OCD is just my way of saying: I know this is irrational, I have no valid reason for (except for the nagging underline that ReSharper adds to my files), and I accept that.
As for the projects vs folders. I typically maintain one config file. If I need more information in a library that would naturally come from the config, I write a config section, just like NHibernate, Log4Net, or subsonic does. They are actually very easy to write — I’ll have to post on that later.
As for the other stuff: that is why I stopped using Nant and Cruise Control. They were maintenance issue. I switch to JetBrains TeamCity and haven’t looked back.
Chris,
This is very useful would mind putting up a sample solution im sure many would find it very useful.
Many thanks
Ismail
Very interesting read indeed.
One little offtopic question: You’ve put emphasize in the fact that the Repository Project is the only project talking to the Database. Lets assume you use Asp.Net Forms authentication, wich needs a DB-Connection. How do you handle this?