After doing a whole bunch of research on Onion Architecture, I have made an attempt at implementing this in a new system that we are developing.
We have the Layers as per below:
- Domain
Domain.Entities
Domain.Interfaces
- Infrastructure
Infrastructure.Data
Infrastructure.DependencyResolution
Infrastructure.Interfaces
Infrastructure.Logging
- Services
Services.Interfaces
- Tests
Tests.Core
- Web
Web.UI
Domain.Entities - All the domain models are kept here.
Base Entity
namespace Domain.Entities
{
public class BaseEntity
{
public virtual Guid ID { get; set; }
public virtual DateTime DateCreated { get; set; }
public virtual DateTime? DeletedDate { get; set; }
public override bool Equals (object obj)
{
if (obj == null)
return false;
var t = obj as BaseEntity;
if (t == null)
return false;
if (ID == t.ID)
return true;
return false;
}
public override int GetHashCode ()
{
int hash = GetType ().GetHashCode ();
hash = (hash * 397) ^ ID.GetHashCode ();
return hash;
}
}
}
Company Entity
namespace Domain.Entities
{
public class Company : BaseEntity
{
public virtual string Name { get; set; }
public virtual IEnumerable<User> Users { get; set; }
public virtual IEnumerable<Branch> Branches { get; set; }
public virtual IEnumerable<Department> Departments { get; set; }
public class CompanyCollection : List<Company>
{
}
}
}
Domain.Interfaces - I have a bunch of generic interfaces programmed here.
IReadRepository
IReadWriteRepsitory
IUnitOfWork
IWriteRepository
IReadRepository
namespace Domain.Interfaces
{
public interface IReadRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> All ();
TEntity FindBy (Expression<Func<TEntity, bool>> expression);
TEntity FindBy (object id);
IQueryable<TEntity> FilterBy (Expression<Func<TEntity, bool>> expression);
}
}
IWriteRepository
namespace Domain.Interfaces
{
public interface IWriteRepository<TEntity> where TEntity : class
{
bool Add (TEntity entity);
bool Add (IEnumerable<TEntity> entities);
bool Update (TEntity entity);
bool Update (IEnumerable<TEntity> entities);
bool Delete (TEntity entity);
bool Delete (IEnumerable<TEntity> entities);
}
}
IReadWriteRepository
namespace Domain.Interfaces
{
public interface IReadWriteRepository<TEntity> :
IReadRepository<TEntity>, IWriteRepository<TEntity> where TEntity : class
{
}
}
IUnitOfWork
namespace Domain.Interfaces
{
public interface IUnitOfWork : IDisposable
{
void Commit ();
void Rollback ();
}
}
Infrastructure.Interfaces
- Store all infrastructure service interfaces here
namespace Infrastructure.Interfaces
{
public interface IConfigService
{
string Connection{ get; }
}
}
namespace Infrastructure.Interfaces
{
public interface ILoggingService
{
bool IsDebugEnabled { get; }
bool IsErrorEnabled { get; }
bool IsFatalEnabled { get; }
bool IsInfoEnabled { get; }
bool IsTraceEnabled { get; }
bool IsWarnEnabled { get; }
void Debug (Exception exception);
void Debug (string format, params object[] args);
void Debug (Exception exception, string format, params object[] args);
void Error (Exception exception);
void Error (string format, params object[] args);
void Error (Exception exception, string format, params object[] args);
void Fatal (Exception exception);
void Fatal (string format, params object[] args);
void Fatal (Exception exception, string format, params object[] args);
void Info (Exception exception);
void Info (string format, params object[] args);
void Info (Exception exception, string format, params object[] args);
void Trace (Exception exception);
void Trace (string format, params object[] args);
void Trace (Exception exception, string format, params object[] args);
void Warn (Exception exception);
void Warn (string format, params object[] args);
void Warn (Exception exception, string format, params object[] args);
}
}
Infrastructure.Data
- I'm using Fluent NHibernate to talk to a postgresql database.
In here I keep my Mapping Files, Unit of work implementation and the Generic repository implementation.
Hibernate Helper
namespace Infrastructure.Data.Helpers
{
public class NHibernateHelper
{
private ISessionFactory _sessionFactory;
private readonly string _connectionString;
public NHibernateHelper (string connectionString)
{
if (string.IsNullOrEmpty (connectionString))
throw new HibernateConfigException ("ConnectionString in Web.config is not set.");
_connectionString = connectionString;
}
public ISessionFactory SessionFactory {
get {
return _sessionFactory ?? (_sessionFactory = InitializeSessionFactory ());
}
}
private ISessionFactory InitializeSessionFactory ()
{
return Fluently.Configure () . . . . . . .;
}
}
}
Unit Of Work
namespace Infrastructure.Data.Helpers
{
public class UnitOfWork : IUnitOfWork
{
private readonly ISessionFactory _sessionFactory;
private readonly ITransaction _transaction;
public ISession Session { get; private set; }
public UnitOfWork (ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
Session = _sessionFactory.OpenSession ();
Session.FlushMode = FlushMode.Auto;
_transaction = Session.BeginTransaction (IsolationLevel.ReadCommitted);
}
public void Commit ()
{
if (!_transaction.IsActive) {
throw new InvalidOperationException ("Oops! We don't have an active transaction");
}
_transaction.Commit ();
}
public void Rollback ()
{
if (_transaction.IsActive) {
_transaction.Rollback ();
}
}
public void Dispose ()
{
if (Session.IsOpen) {
Session.Close ();
Session = null;
}
}
}
}
Base Mapping
namespace Infrastructure.Data.Mapping
{
public class BaseMapping : ClassMap<BaseEntity>
{
public BaseMapping ()
{
UseUnionSubclassForInheritanceMapping ();
Id (x => x.ID);
Map (x => x.DateCreated);
Map (x => x.DeletedDate);
}
}
}
Company Mapping
namespace Infrastructure.Data.Mapping
{
public class CompanyMapping : SubclassMap<Company>
{
public CompanyMapping ()
{
Abstract ();
Map (x => x.Name);
HasManyToMany<User> (x => x.Users).Table ("CompanyUser").Inverse ();
HasMany<Branch> (x => x.Branches).Inverse ().Cascade.All ();
HasMany<Department> (x => x.Departments).Inverse ().Cascade.All ();
Table ("Company");
}
}
}
Repository Implementation
namespace Infrastructure.Data.Repositories
{
public class Repository<TEntity> : IReadWriteRepository<TEntity>
where TEntity : class
{
private readonly ISession _session;
public Repository (ISession session)
{
_session = session;
}
#region IWriteRepository
public bool Add (TEntity entity)
{
_session.Save (entity);
return true;
}
public bool Add (System.Collections.Generic.IEnumerable<TEntity> entities)
{
foreach (TEntity entity in entities) {
_session.Save (entity);
}
return true;
}
public bool Update (TEntity entity)
{
_session.Update (entity);
return true;
}
public bool Update (System.Collections.Generic.IEnumerable<TEntity> entities)
{
foreach (TEntity entity in entities) {
_session.Update (entity);
}
return true;
}
public bool Delete (TEntity entity)
{
_session.Delete (entity);
return true;
}
public bool Delete (System.Collections.Generic.IEnumerable<TEntity> entities)
{
foreach (TEntity entity in entities) {
_session.Delete (entity);
}
return true;
}
#endregion
#region IReadRepository
public System.Linq.IQueryable<TEntity> All ()
{
return _session.Query<TEntity> ();
}
public TEntity FindBy (System.Linq.Expressions.Expression<System.Func<TEntity, bool>> expression)
{
return FilterBy (expression).SingleOrDefault ();
}
public TEntity FindBy (object id)
{
return _session.Get<TEntity> (id);
}
public System.Linq.IQueryable<TEntity> FilterBy (System.Linq.Expressions.Expression<System.Func<TEntity, bool>> expression)
{
return All ().Where (expression).AsQueryable ();
}
#endregion
}
}
Finally, the service for dependency injection:
namespace Infrastructure.Data.Services
{
public class ConfigService : IConfigService
{
#region IConfigService implementation
public string Connection {
get {
string strConnectionString = null;
var connectionSettings = ConfigurationManager.ConnectionStrings ["Connection"];
if (connectionSettings != null) {
strConnectionString = connectionSettings.ConnectionString;
}
return strConnectionString;
}
}
#endregion
}
}
Infrastructure.DependencyResolution
- Using Simple Injector
In here I store all my registration packages / modules
ConfigPackage
namespace Infrastructure.DependecyResolution
{
public class ConfigPackage : IPackage
{
#region IPackage implementation
public void RegisterServices (SimpleInjector.Container container)
{
container.Register<IConfigService,ConfigService> ();
}
#endregion
}
}
RepositoryPackage
namespace Infrastructure.DependecyResolution
{
public class RepositoryPackage : IPackage
{
#region IPackage implementation
public void RegisterServices (SimpleInjector.Container container)
{
container.RegisterPerWebRequest<ISessionFactory> (() => {
var configPackage = container.GetInstance<IConfigService> ();
NHibernateHelper objNHibernate = new NHibernateHelper (configPackage.Connection);
return objNHibernate.SessionFactory;
});
container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork> ();
container.RegisterPerWebRequest<ISession> (() => {
UnitOfWork unitOfWork = (UnitOfWork)container.GetInstance<IUnitOfWork> ();
return unitOfWork.Session;
});
container.RegisterOpenGeneric (typeof(IReadWriteRepository<>), typeof(Repository<>));
}
#endregion
}
}
LoggingPackage
namespace Infrastructure.DependecyResolution
{
public class LoggingPackage : IPackage
{
#region IPackage implementation
public void RegisterServices (SimpleInjector.Container container)
{
ILoggingService logger = GetLoggingService ();
container.Register<ILoggingService> (() => logger);
}
#endregion
private ILoggingService GetLoggingService ()
{
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition ("utc_date", typeof(UtcDateRenderer));
ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition ("web_variables", typeof(WebVariablesRenderer));
ILoggingService logger = (ILoggingService)LogManager.GetLogger ("NLogLogger", typeof(LoggingService));
return logger;
}
}
}
Infrastructure.Logging
- using NLog
In here is just the implementation of the logging interface.
Services.Interfaces
- Applicaiton Service interfaces gets stored here
namespace Services.Interfaces
{
public interface ICompanyService
{
IQueryable<Company> GetCompanies ();
Company GetCompany (Guid guidId);
void CreateNewCompany (Company company);
void UpdateExistingCompany (Company company);
void DeleteCompany (Company company);
}
}
Tests.Core
Repository and Service Tests are in here at this stage, sure there will be more tests going in here.
Web.UI
- ASP.NET MVC 4 Project as user interface application
Implementation of the Company Service Interface.
namespace Web.UI.Services
{
public class CompanyService : ICompanyService
{
private IUnitOfWork _unitOfWork;
private IReadWriteRepository<Company> _companyRepository;
public CompanyService (IUnitOfWork unitOfWork, IReadWriteRepository<Company> companyRepository)
{
_unitOfWork = unitOfWork;
_companyRepository = companyRepository;
}
#region ICompanyService implementation
public IQueryable<Company> GetCompanies ()
{
return _companyRepository.All ().Where (x => !x.DeletedDate.HasValue);
}
public Company GetCompany (Guid guidId)
{
return _companyRepository.FindBy (guidId);
}
public void CreateNewCompany (Company company)
{
if (_companyRepository.Add (company))
_unitOfWork.Commit ();
else
_unitOfWork.Rollback ();
}
public void UpdateExistingCompany (Company company)
{
if (_companyRepository.Update (company))
_unitOfWork.Commit ();
else
_unitOfWork.Rollback ();
}
public void DeleteCompany (Company company)
{
if (_companyRepository.Update (company))
_unitOfWork.Commit ();
else
_unitOfWork.Rollback ();
}
#endregion
}
}
The Inject the ICompanyService
into the Controller. Create a view model for Company which the view can manipulate.
Simple Injector Service also gets initialized in this project. While it gets initialized I register the Services.Interfaces to Web.UI.Services
implementation.
That is pretty much how I have implemented the Onion Architecture. Let me know if I went wrong somewhere. This is the first time I set up an application with a proper architecture.