Clean Architecture as a term is around for a while. However, the path to implement it is not always clear nor easy to follow. When projects fail for reasons that are primary technical, the reason is often uncontrolled complexity. The complexity goes out of hand when the code lacks structure, when it lacks Clean Architecture.
In this session, I will show how to achieve consistency by implementing Clean Architecture through structure, rather than relying on discipline only. We will look at some basic building blocks of an application infrastructure which will enforce the way dependencies are created, how dependency injection is used or how separation of the data access concerns is enforced.
3. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Co-Founder & Partner
About me
@florincoros
Partner
https://onCodeDesign.com/training
Co-Founder & Partner
4. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing Clean Architecture
Florin CoroÈ
onCodeDesign.com | www.infiniswiss.com | www.iquarc.com
florin@onCodeDesign.com
@florincoros
.com
5. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Why is Architecture Important
-- Robert C. Martin, Clean Architecture
The goal of software architecture is to minimize
the human resources required to build and
maintain the required system
7. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Manage the Complexity and Size
when projects do fail for reasons that are
primarily technical, the reason is often
uncontrolled complexity
8. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
The Architecture is Separation of Concerns and a Set of Rules
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
9. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing the Architecture
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
10. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Structure that Supports the Architecture
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
11. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Clean Architecture
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
Presenter
Controller
UseCase
Implementation
Presenter
Controller
UseCase
Implementation
UseCase
InputAbstraction
UseCase
OutputAbstraction
Dependencies should be in one direction only
Source code dependencies can only point inwards
12. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing Clean Architecture through Structure
âą Hide external frameworks to enforce the way they are used
âą Use assemblies and references among them to enforce rules
âą Enforce Constructor Dependency Injection that encourages
Programming Against Interfaces
Create a structure that makes it difficult to write bad code and it makes it easy to write good
code, code that follows the architecture and the design
13. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Creating a Structure for Clean Architecture
<<interface>><<interface>>
ILogger
+LogError()
Logger
+LogError()
<<public interface>><<public interface>>
ILogger
+LogError()
<<Internal class>><<Internal class>>
Logger
+LogError()
14. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Hide External Libraries from App Code
<<Internal class>><<Internal class>>
Exception Wrappers
<<public
Interface>>
<<public
Interface>>
API Interfaces
<<Internal class>><<Internal class>>
Decorators
15. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
<<Interface>>
TDataModel
IRepository
+GetEntities<TDataModel>()
<<Interface>>
TDataModel
IUnitOfWork
+GetEntities<TDataModel>()
+SaveChanges()
Database
EfRepository EfUnitOfWork
<<Stereotype>>
<<DTO>>
Order
<<DTO>>
Person
Enforce Separation of Data Access Concerns
iQuarcDataAccess
16. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
DIP to Enforce Separation of Data Access Concern
<<Interface>>
IDbContextFactory
+CreateContext()
Database
<<DTO>>
Customer<<DTO>>
Order<<DTO>>
Person
UnitOfWork
Repository DbContextFactory
<<Interface>>
TDataModel
IRepository, IUnitOfWork
+GetEntities()
+SaveEntities()
17. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
AppBoot: DI Abstractions & Type Discovery
<<Interface>>
TDataModel
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
<<Interface>>
TDataModel
IDbContextFactory
+CreateContext()
Database
<<DTO>><<DTO>>
Customer<<DTO>><<DTO>>
Order<<DTO>><<DTO>>
Person
<<DTO>>
Customer<<DTO>>
Order<<DTO>>
Person
UnitOfWork
Repository
UnitOfWork
Repository DbContextFactory
<<Attribute>><<Attribute>>
ServiceAttribute
18. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
<<Attribute>>
ServiceAttribute
+ServiceAttribute(interface : Type )
Bootstrapper
+Run()
<<Internal>>
DependencyContainerAdapter
iQuarcAppBoot
AppBoot Hides the DI Framework under Abstractions
public interface IPriceCalculator
{
int CalculateTaxes(Order o, Customer c);
int CalculateDiscount(Order o, Customer c);
}
[Service(typeof(IPriceCalculator), Lifetime.Instance)]
interal class PriceCalculator : IPriceCalculator
{
public int CalculateTaxes(Order o, Customer c)
{
return 10; // do actual calculation
}
public int CalculateDiscount(Order o, Customer c)
{
return 20; // do actual calculation
}
}
19. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Patters for Creating Services that Depend Only on Interfaces
[Service(typeof (IOrderingService))]
private class OrderingService : IOrderingService
{
private readonly IRepository repository;
private readonly IPriceCalculator calculator;
private readonly IApprovalService orderApproval;
public OrderingService(IRepository repository, IPriceCalculator calculator, IApprovalService orderApproval)
{
this.repository = repository;
this.calculator = calculator;
this.orderApproval = orderApproval;
}
public SalesOrderInfo[] GetOrdersInfo(string customerName)
{
var orders = repository.GetEntities<SalesOrderHeader>()
...
return orders.ToArray();
}
public SalesOrderResult PlaceOrder(string customerName, OrderRequest request)
{
...
}
}
20. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Enforce Constructor DI to Prevent Circular Dependencies
[Service(typeof(IApprovalService))]
class ApprovalService : IApprovalService
{
private readonly IPriceCalculator priceCalculator;
public ApprovalService(IPriceCalculator priceCalculator)
{
this.priceCalculator = priceCalculator;
}
...
}
[Service(typeof (IPriceCalculator), Lifetime.Instance)]
public class PriceCalculator : IPriceCalculator
{
private readonly IApprovalService approvalService;
public PriceCalculator(IApprovalService approvalService)
{
this.approvalService = approvalService;
}
...
}
22. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
public interface IRepository
{
IQueryable<TDbEntity> GetEntities<TDbEntity>() where TDbEntity : class;
IUnitOfWork CreateUnitOfWork();
}
public interface IUnitOfWork : IRepository, IDisposable
{
void SaveChanges();
void Add<T>(T entity) where T : class;
void Delete<T>(T entity) where T : class;
void BeginTransactionScope(SimplifiedIsolationLevel isolation);
}
Create Separated Patterns for Read-Only and
Read-Write
23. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Patterns for Read-Only data
public class OrdersController : Controller
{
private readonly IRepository repository;
public OrdersController(IRepository repository)
{
this.repository = repository;
}
public IActionResult Index(string customer)
{
var orders = repository.GetEntities<SalesOrderHeader>()
.Where(soh => soh.Customer.Person.LastName == customer)
.Select(soh => new OrdersListViewModel
{
CustomerName = customer,
Number = soh.SalesOrderNumber,
SalesPersonName = soh.SalesPerson,
DueDate = soh.DueDate,
});
return View(orders);
}
...
24. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Patterns for Read-Write data
public class OrdersController : Controller
{
...
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult PlaceOrder(OrderRequestViewModel model)
{
...
using (IUnitOfWork uof = repository.CreateUnitOfWork())
{
SalesOrderHeader order = uof.GetEntities<SalesOrderHeader>()
.FirstOrDefault(o => o.CustomerID == c.ID && o.OrderDate.Month == DateTime.Now.Month);
if (order == null)
{
order = new SalesOrderHeader {Customer = c};
uof.Add(order);
}
AddRequestToOrder(model, order);
uof.SaveChanges();
}
...
}
}
25. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Consistency Creates Optimizations Opportunities
internal class EfRepository : IRepository, IDisposable
{
public IQueryable<T> GetEntities<T>() where T : class
{
return Context.Set<T>().AsNoTracking();
}
public IUnitOfWork CreateUnitOfWork()
{
return new EfUnitOfWork(contextFactory, interceptorsResolver);
}
private sealed class EfUnitOfWork : IUnitOfWork
{
private DbContext context;
private TransactionScope transactionScope;
private readonly IDbContextFactory contextFactory;
public EfUnitOfWork(IDbContextFactory contextFactory, IInterceptorsResolver resolver)
{
this.contextFactory = contextFactory;
this.interceptorsResolver = interceptorsResolver;
}
}
27. @ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing Clean Architecture through Structure
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
<<Attribute>><<Attribute>>
ServiceAttribute
RepositoryImpl
+ GetEntities<T>() : IQueriable()
+ SaveChanges()
Hide external frameworks to enforce the way they are used
Use assemblies and references among them to enforce rules
Enforce Constructor Dependency Injection that encourages Programming Against
Interfaces
/iQuarc
iQuarc
onCodeDesign.com/training