I have created several middle size applications with the following approach and sleep very well at night ;-)
Entity <=> Service <=> Model
Please note, those 3 layers must be separated by Packages/Modules/Assemblies and be a backend only.
Entity: Domain Layer, Domain Object, Repository, Database Model, Data Object Layer (DAL)
Service: Business Logic Layer (BLL), Business Service Layer
Model: Data Transfer Object (DTO), Presentation Layer, View Model (please remove presentation/view from your dictionary)
View is a separate project (PWA, Progressive Web Application) which uses API and does not require any backend code support. It has its own View Models and Business Logics inside.
Example:
MyProject.Entities (DAL):
[Table("customers")]
public class Customer
{
// Used to instantiate the proxy by DbContext
protected Customer() { }
// Used by developers
public Customer(string name) : this()
{
Id = IdGenerator.Next();
SetName(name);
}
[Key]
public virtual string Id { get; protected set; }
public virtual string Name { get; protected set; }
[Column("summary")]
public virtual string Description { get; set; }
public void SetName(string name)
{
if (string.IsNullOrEmpty())
throw new ArgumentNullException(nameof(name));
Name = name;
}
}
MyProject.Models (DTO):
public class IdModel
{
[Required]
public string Id { get; set; }
}
public class CreateCustomerModel
{
[Required]
[MaxLength(50)]
public string Name { get; set; }
}
public class EditCustomerModel : IdModel
{
[Required]
[MaxLength(50)]
public string Name { get; set; }
public string Description { get; set; }
}
MyProject.Services (BLL):
public class CustomerService
{
private readonly DbContext _db;
private readonly IMapper _mapper;
public CustomerService(DbContext db, IMapper mapper)
{
_db = db;
_mapper = mapper;
}
public async Task<EditCustomerModel> Get(IdModel model)
{
var customer = await _db.Get<Customer>(model.Id);
return _mapper.Map<EditCustomerModel>(customer);
}
public async Task<IdModel> Create(CreateCustomerModel model)
{
var customer = new Customer(model.Name);
await _db.SaveOrUpdate(customer);
return _mapper.Map<IdModel>(customer);
}
public async Task Edit(EditCustomerModel model)
{
var customer = await _db.Get<Customer>(model.Id);
customer.SetName(model.Name);
customer.Description = model.Description;
await _db.SaveOrUpdate(customer);
}
public async Task Delete(IdModel model)
{
await _db.Remove<Customer>(model.Id);
}
}
The above example is just a simple CRUD service. But in the same way you can implement any Domain Services eg.
CustomerInvoiceService
:
[Description("Creates a new customer and invoices it. Returns invoice Id.")]
public async Task<IdModel> CreateCustomerAndInvoiceIt(CreateCustomerInvoiceModel model)
{
model.InvoiceForm.CustomerId = await _customerService.Create(model.CustomerForm);
var invoiceId = await _invoiceService.Create(model.InvoiceForm);
return invoiceId;
}
Just expose API/services and let frontend devs freely do their job completely separated from the backend. If it's a desktop app or the front end is made using backend language like c#/java/Go (which is very bad), they must build their own Frontend Logic Layers and Models and never allow them to mix Domain/Business Layers with Presentation Layers together in any situation. Frontend is a completely separated thing and, as someone mentioned Views above, I would suggest to remove that word from the BLL dictionary at all.
IMHO