I'm actually recreating the default architecture of my projects.
In this post I want you not just help me,but think,tell and improve presented ways. This can be very useful challenge.
This default architecture can be freely shared with all.
So here is description:
Basically MVC is our point of view. We follow OOP and layer programming concepts. Also we will use SQL Server and EF DB First.
So here is what ive done till now:
Ive created 4 layers in my solution:
- Domain: JUST domain classes
- DAL: responsible for accessing data, containing UOW and repositories and data access related validations.
- BL: unique responsible of business logic and unique boss of DAL.
- UI: which is not so important! (as even it maybe a console app!)
Ive implemented BL with generic funcs like getAll
and getById
and crud funcs.these funcs would call DAL generic funcs which is also ready.
Now an important question is: is it right to implement DAL funcs as GENERIC?
Please consider this scenario :
From UI we post a model to action and in action we call a BL Func (BL.insert
(model)). The BL.Insert
func would call something like DAL.Add
(model) (notice BL will call DAL once and will tell it what it wants). now the DAL.Add
func would have to insert 3 records into 3 different tables (model is passed from UI layer).
I think this cant be achieved by DAL GENERIC funcs. So what is the correct and standard way of implementing Layers and relationships with noticing the Separation Of Concerns?
In my action i would have:
[HttpPost]
public ActionResult Insert()
{
Bl.EntityBase.Article.Insert(new Article());
return RedirectToAction("Index");
}
in BL I would have:
public void Insert(T obj)
{
Da.Repository<T>().Insert(obj);
}
And in my DAL I have:
public virtual void Insert(T entity)
{
DbEntityEntry dbEntityEntry = Db.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
Set.Add(entity);
}
}
You should implement Repository pattern with generic repositories in you DAL .
In order to put abstraction you have to use IRepository interface an its implementation should be injected using dependency resolver .
Also you can achieve the same in BL(Service) using interface .
Here is a good discussion : Difference between Repository and Service Layer?
You can also improve it using Unit Of Work pattern.
Here is a complete code snippet which shows how can we achieve abstraction between multiple layers :
Repository
public interface IRepository<T> where T : class
{
DbContext GetContext();
IQueryable<T> GetAll();
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
void Add(T entity);
void Delete(T entity);
void DeleteAll(IEnumerable<T> entity);
void Edit(T entity);
bool Any();
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly DbContext _context;
private readonly IDbSet<T> _dbset;
public Repository(DbContext context)
{
_context = context;
_dbset = context.Set<T>();
}
public virtual DbContext GetContext()
{
return _context;
}
public virtual IQueryable<T> GetAll()
{
return _dbset;
}
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
var query = _dbset.Where(predicate).AsQueryable();
return query;
}
public virtual void Add(T entity)
{
_dbset.Add(entity);
}
public virtual void Delete(T entity)
{
var entry = _context.Entry(entity);
entry.State = EntityState.Deleted;
_dbset.Remove(entity);
}
public virtual void DeleteAll(IEnumerable<T> entity)
{
foreach (var ent in entity)
{
var entry = _context.Entry(ent);
entry.State = EntityState.Deleted;
_dbset.Remove(ent);
}
}
public virtual void Edit(T entity)
{
var entry = _context.Entry(entity);
_dbset.Attach(entity);
entry.State = EntityState.Modified;
}
public virtual bool Any()
{
return _dbset.Any();
}
}
UOW
public interface IUnitOfWork : IDisposable
{
IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;
void Save();
}
public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext, new()
{
private readonly DbContext _ctx;
private readonly Dictionary<Type, object> _repositories;
private bool _disposed;
public UnitOfWork()
{
_ctx = new TContext();
_repositories = new Dictionary<Type, object>();
_disposed = false;
}
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
// Checks if the Dictionary Key contains the Model class
if (_repositories.Keys.Contains(typeof (TEntity)))
{
// Return the repository for that Model class
return _repositories[typeof (TEntity)] as IRepository<TEntity>;
}
// If the repository for that Model class doesn't exist, create it
var repository = new Repository<TEntity>(_ctx);
// Add it to the dictionary
_repositories.Add(typeof (TEntity), repository);
return repository;
}
public void Save()
{
// save all changes together
_ctx.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_ctx.Dispose();
}
_disposed = true;
}
}
}
Service
public interface IService
{
IList<Users> GetUserDetails(int userId);
}
public class Service : IService
{
private readonly IRepository<Users> _userRepository;
public Service (IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_userRepository=_unitOfWork.GetRepository<Users>();
}
public IList<Users> GetUserDetails(int userId)
{
return _userRepository.GetAll();
}
}
Controller
public class HomeController
// use the same patter which has been used in service
{
private readonly IService _service;
public HomeController(IService service)
{
_service=service;
}
public ActionResult Index()
{
return _service. GetUserDetails(/*userid*/);
}
}
Hope this code will help many users and you of course.
Before we begin, it might make sense to separate the notions of contracts from layers. You listed 4 layers, but one could also describe them as 3 layers and one contract schema (the domain contract schema) which is cross cutting containing concerns for the DAL, BL and potentially higher level layers.
- Domain: JUST domain classes
- DAL: responsible for accessing data, containing UOW and repositories and data access related validations.
- BL: unique responsible of business logic and unique boss of DAL.
- UI: which is not so important! (as even it maybe a console app!)
With regards to typical LOB style SOA n-tier architectures, where the services may be consumed by other teams or clients, generally speaking it may make sense to strive for upwards of three contractual schemas.
The first are the contract schemas between the consumers and the services that you are looking to provide. These may be versioned. These schemas may be use case specific and provide "representations" of the underlying entity schema in various forms of normalization and composition. These schemas may even include supporting meta data for supporting use case specific requirements, such as population of lookup lists and indicating use case specific user permissions.
The second is the entity domain schema that all of your current implementation operates on. This is always current and represents the logical schema in an object oriented design fashion.
The third is the physical schema where your data resides. This is always current and contains your data in whatever appropriate design fashion the storage technology dictates, whether that be in relational design fashion or otherwise. Note that while generally the physical schema and the logical schema may look similar or even identical, sometimes there are restrictions in the physical storage technology that may require that the physical schema be different. For example, you should not have more than 8060 bytes per row in a MS Sql Server table, and in some cases a single logical entity data object may be stored in multiple one to one related physical tables if MS Sql Server is used.
With these three schemas in mind, you can then architect your solution to address the concerns of supporting these schemas.
To address storage and management of the physical schema select a storage technology/strategy (i.e.: Sql Server, Oracle, MySql, xml, MongoDb, etc.)
To address I/O to the physical schema and to translate from the physical schema to the entity schema, you introduce an IDataAccess layer interface.
To address the business rules that must always be enforced around an entity or set of entities regardless of use case, you introduce the IBusiness layer interface.
To address the rules that are use case specific that you do not want to have to repeat between multiple clients and to translate between the entity schema and the service contract schemas, you introduce the IApplicationService layer interface.
To address exposing the service and its contract schema transport objects externally, you introduce the appropriate host controller classes (ApplicationServiceController, ApplicationServiceWCFHost, or ApplicationService.asmx, etc..).
Here are a set of interfaces contained within a generic namespace strategy that you could implement to provide you with the abstractions for the entity schema and the data access and business layers.
AcmeFrameworkContracts.dll
public class Response {}
public class Response<TResponse> : Response where TResponse : Response<TResponse> {}
AcmeFramework.dll
references: AcmeFrameworkContracts.dll
namespace AcmeFramework
{
namespace Entity
{
public abstract class Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>
where TEntity : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>
where TDataObject : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.BaseDataObject
where TDataObjectList : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.BaseDataObjectList, new()
where TIBusiness : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.IBaseBusiness
where TIDataAccess : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TPrimaryKey>.IBaseDataAccess
where TPrimaryKey : IComparable<TPrimaryKey>, IEquatable<TPrimaryKey>
{
public class BaseDataObject
{
public TPrimaryKey Id;
}
public class BaseDataObjectList : CollectionBase<TDataObject>
{
public TDataObjectList ShallowClone() { ... }
}
public interface IBaseBusiness
{
TDataObjectList LoadAll();
TDataObject LoadById(TPrimaryKey id);
TDataObject LoadByIds(IEnumerable<TPrimaryKey> ids);
IQueryable<TDataObject> Query();
IQueryable<TDataObject> FindBy(Expression<Func<T, bool>> predicate);
void Delete(TPrimaryKey ids);
void Delete(IEnumerable<TPrimaryKey> ids);
void Save(TDataObject entity);
void Save(TDataObjectList entities);
ValidationErrors Validate(TDataObject entity); // <- Define ValidationErrors as you see fit
ValidationErrors Validate(TDataObjectList entities); // <- Define ValidationErrors as you see fit
}
public abstract BaseBusiness : IBaseBusiness
{
private TIDataAccess dataAccess;
protected BaseBusiness(TIDataAccess dataAccess) { this.dataAccess = dataAccess; }
}
public interface IBaseDataAccess
{
IQueryable<TDataObject> Query();
IQueryable<TDataObject> FindBy(Expression<Func<T, bool>> predicate);
void DeleteBy(Expression<Func<T, bool>> predicate);
void Save(TDataObjectList entities);
}
}
}
namespace Application
{
public interface IBaseApplicationService {}
public class BaseApplicationServiceWebAPIHost<TIApplicationService> : ApiController where TIApplicationService : IBaseApplicationService
{
private TIApplicationService applicationService;
public BaseApplicationServiceWebAPIHost(TIApplicationService applicationService) { this.applicationService = applicationService; }
}
public class BaseApplicationServiceWCFHost<TIApplicationService> where TIApplicationService : IBaseApplicationService
{
private TIApplicationService applicationService;
public BaseApplicationServiceWCFHost(TIApplicationService applicationService) { this.applicationService = applicationService; }
}
}
Use it like so:
UserDomain.dll
references: AcmeFramework.dll
namespace UserDomain
{
public class User : Entity<User, User.DataObject, User.DataObjectList, User.IBusiness, User.IDataAccess, Guid>
{
public class DataObject : BaseDataObject
{
public string FirstName;
public string LastName;
public bool IsActive { get; }
}
public class DataObjectList : BaseDataObjectList {}
public interface IBusiness : IBaseBusiness
{
void DeactivateUser(Guid userId);
}
public interface IDataAccess : IBaseDataAccess {}
public class Business
{
public Business(User.IDataAccess dataAccess) : base(dataAccess) {}
public void DeactivateUser(Guid userId)
{
...
}
}
}
public class UserPermission : Entity<UserPermission, UserPermission.DataObject, UserPermission.DataObjectList, UserPermission.IBusiness, UserPermission.IDataAccess, Guid>
{
public class DataObject : BaseDataObject
{
public Guid PermissionId;
public Guid UserId;
}
public class DataObjectList : BaseDataObjectList {}
public interface IBusiness : IBaseBusiness {}
public interface IDataAccess : IBaseDataAccess {}
public class Business
{
public Business(UserPermission.IDataAccess dataAccess) : base(dataAccess) {}
}
}
public class Permission : Entity<Permission, Permission.DataObject, Permission.DataObjectList, Permission.IBusiness, Permission.IDataAccess, Guid>
{
public class DataObject : BaseDataObject
{
public string Code;
public string Description;
}
public class DataObjectList : BaseDataObjectList {}
public interface IBusiness : IBaseBusiness {}
public interface IDataAccess : IBaseDataAccess {}
public class Business
{
public Business(Permission.IDataAccess dataAccess) : base(dataAccess) {}
}
}
}
UserManagementApplicationContracts.dll
references: AcmeFrameworkContracts.dll
namespace UserManagement.Contracts
{
public class ReviewUserResponse : Response<ReviewUserResponse>
{
public class UserPermission
{
public Guid id;
public string permission; // <- formatted as "permissionCode [permissionDescription]"
}
public class User
{
public Guid id;
public string firstName;
public string lastName;
public List<UserPermissions> permissions;
}
public User user;
}
public class EditUserResponse : Response<EditUserResponse>
{
public class Permission
{
public Guid id;
public string permissionCode;
public string description;
}
public class UserPermission
{
public Guid id;
public Guid permissionId;
}
public class User
{
public Guid id;
public string firstName;
public string lastName;
public List<UserPermissions> permissions;
}
public List<Permission> knownPermissions;
public User user;
}
public interface IUserManagementApplicationService : IBaseApplicationService
{
Response EditUser(Guid userId);
Response SaveUser(EditUserResponse.user user);
Response ViewUser(Guid userId);
}
}
UserManagementApplicationImplementation.dll
references: AcmeFramework.dll, AcmeFrameworkContracts.dll, UserManagementApplicationContracts.dll, UserDomain.dll
namespace UserManagement.Implementation
{
public class UserManagementApplicationService : IUserManagementApplicationService
{
private User.IBusiness userBusiness;
private UserPermissions.IBusiness userPermissionsBusiness;
private Permission.IBusiness permissionBusiness;
public UserManagementApplicationService(User.IBusiness userBusiness, UserPermission.IBusiness userPermissionsBusiness, Permission.IBusiness permissionBusiness)
{
this.userBusiness = userBusiness;
this.userPermissionsBusiness = userPermissionsBusiness;
this.permissionBusiness = permissionBusiness;
}
public Response EditUser(Guid userId)
{
...
}
public Response SaveUser(EditUserResponse.user user)
{
...
}
public Response ViewUser(Guid userId)
{
...
}
}
}
You can layer in UOW if you are so inclined. You can also expose services and hosts that are more logical entity specific instead of use case specific as you feel the need, but remember to not place your domain schema classes in your contracts library. That way you can evolve the contracts independently from the underlying implementations. And you can move the Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess>
subclasses to a domain contracts library such as UserDomain.Contracts.dll, and leave the implementations in the domain libraries such as UserDomain.dll if you desire that isolation.