I will present a baseline architecture template that our team will be using for new applications. It is a good place to start, but things might change a bit as we delve deeper into each piece of the puzzle.
Technologies
Database
In most cases the application should implement all its logic without depending on the database, but there are a few when the compromise is ok:
- stored procedures for complex, independent operations (for example a search functionality, which must work as fast as possible and does not depend on other business functionalities).
- triggers for simple, independent operations (counting how many X relate to Y and keeping the information updated in a column, for example)
Application projects / layers
Common: classes which can be used by any of the application layers.
A few examples of classes from this project are:
- LinqLogger
- Log
- RegexHelper
- StringExtensions
Data: the BusinessEntities and Data Access layer classes
The project contains the .dbml file for Linq2SQL, and partial classes for the entities. Other things included are:
- DLinq (http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx)
- Cache helpers
- Paged list (http://blog.wekeroad.com/2007/12/10/aspnet-mvc-pagedlistt/, http://www.squaredroot.com/post/2008/04/Updated-PagedList-Class.aspx)
- ContextUnitOfWork (per request, thread specific data context)
- stores the current context in HttpContext.Current, or in the current Thread
- provides the context from that location
- repositories
- BaseRepository<T>
- uses the current DataContext to provide its services
- contains general repository functions (add, delete, getAll, etc)
- has a SubmitChanges method, which always uses a transaction
- IEntityRepository<T>
- an interface for each repository, which helps in using a DI framework and easily creating unit tests
- EntityRepository<T>
- offers specialized methods for that entity
- for cases when specific load options are needed (for example, we load entity A which contains a collection of AXB, and we need each AXB to load its correspondent B also), a punctual DataContext with specific LoadOptions will be used; these cases are usually needed for presenting the information in the UI in a certain way, so they should not interfere with the normal business flow; at this point, Linq2SQL lacks the option to solve these problems directly by configuring load options at any time
- data transfer objects (intermediary classes used to transfer data between the business layer and the UI)
Service (Business Logic Layer):
- IXService: an interface for each service, which helps in using a DI framework and easily creating unit tests
- EntityService
- provide business services for one entity
- contains only the EntityRepository for that entity, to ensure it does not try to encapsulate more logic than it should
- FunctionalityService
- provide business services for one functionality
- can contain more than one EntityRepository
- Infrastructure: classes for working with files and directories, datetime, etc.; classes for working with files and directories, datetime, etc.
Web:
- PageBase class
- Controls directory
- css directory
- some helpers (QueryStringHelper, for example)
- Images directory
- js directory
- PageName.js (a separate .js file for pages which need custom client-side code)
- PageName.aspx
- PageName.asmx or FunctionalityName.asmx (web service which handles ajax requests for PageName or for a functionality)
Other observations
Validation
Transfer (between inferior layers and UI) and presentation of messages: a custom mechanism is needed, because there are no out-of-the box solutions or frameworks