@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing Clean Architecture
Florin Coroș
onCodeDesign.com | www.infiniswiss.com | www.iquarc.com
florin@onCodeDesign.com
@florincoros
.com
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Many thanks to our sponsors & partners!
GOLD
SILVER
PARTNERS
PLATINUM
POWERED BY
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Co-Founder & Partner
About me
@florincoros
Partner
https://onCodeDesign.com/training
Co-Founder & Partner
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing Clean Architecture
Florin Coroș
onCodeDesign.com | www.infiniswiss.com | www.iquarc.com
florin@onCodeDesign.com
@florincoros
.com
@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
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Decompose - Separation of Concerns
Title
Title
Title
Title
Title
Title
Title
@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
@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
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Implementing the Architecture
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Structure that Supports the Architecture
External
Interfaces
UI Frameworks
Devices
Controllers
EntitiesEntities
Use Cases
@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
@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
@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()
@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
@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
@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()
@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
@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
}
}
@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)
{
...
}
}
@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;
}
...
}
@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
Encapsulate Data Access Concerns
iQuarcDataAccess
[Service(typeof (IRepository))]
internal class EfRepository : IRepository, IDisposable
{
private readonly IDbContextFactory contextFactory;
private readonly IInterceptorsResolver interceptorsResolver;
private DbContext context;
private readonly IEnumerable<IEntityInterceptor> interceptors;
public EfRepository(IDbContextFactory contextFactory,
IInterceptorsResolver resolver)
{
this.contextFactory = contextFactory;
this.interceptorsResolver = interceptorsResolver;
this.interceptors =
resolver.GetGlobalInterceptors();
}
...
}
@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
@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);
}
...
@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();
}
...
}
}
@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;
}
}
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Create Development Patterns in Code
@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
@ITCAMPRO #ITCAMP19Community Conference for IT Professionals
Florin Coroș
Author & Trainer, onCodeDesign onCodeDesign.com
Co-founder & Partner, InfiniSwiss www.infiniswiss.com
Co-founder & Partner, iQuarc www.iquarc.com
email: florin@onCodeDesign.com
blog: onCodeDesign.com
tweet: @florincoros
https://onCodeDesign.com/training

ITCamp 2019 - Florin Coros - Implementing Clean Architecture

  • 1.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Implementing Clean Architecture Florin Coroș onCodeDesign.com | www.infiniswiss.com | www.iquarc.com florin@onCodeDesign.com @florincoros .com
  • 2.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Many thanks to our sponsors & partners! GOLD SILVER PARTNERS PLATINUM POWERED BY
  • 3.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Co-Founder & Partner About me @florincoros Partner https://onCodeDesign.com/training Co-Founder & Partner
  • 4.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Implementing Clean Architecture Florin Coroș onCodeDesign.com | www.infiniswiss.com | www.iquarc.com florin@onCodeDesign.com @florincoros .com
  • 5.
    @ITCAMPRO #ITCAMP19Community Conferencefor 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
  • 6.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Decompose - Separation of Concerns Title Title Title Title Title Title Title
  • 7.
    @ITCAMPRO #ITCAMP19Community Conferencefor 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 Conferencefor 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 Conferencefor IT Professionals Implementing the Architecture External Interfaces UI Frameworks Devices Controllers EntitiesEntities Use Cases
  • 10.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Structure that Supports the Architecture External Interfaces UI Frameworks Devices Controllers EntitiesEntities Use Cases
  • 11.
    @ITCAMPRO #ITCAMP19Community Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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; } ... }
  • 21.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals <<Interface>> TDataModel IRepository +GetEntities<TDataModel>() <<Interface>> TDataModel IUnitOfWork +GetEntities<TDataModel>() +SaveChanges() Database EfRepository EfUnitOfWork <<Stereotype>> <<DTO>> Order <<DTO>> Person Encapsulate Data Access Concerns iQuarcDataAccess [Service(typeof (IRepository))] internal class EfRepository : IRepository, IDisposable { private readonly IDbContextFactory contextFactory; private readonly IInterceptorsResolver interceptorsResolver; private DbContext context; private readonly IEnumerable<IEntityInterceptor> interceptors; public EfRepository(IDbContextFactory contextFactory, IInterceptorsResolver resolver) { this.contextFactory = contextFactory; this.interceptorsResolver = interceptorsResolver; this.interceptors = resolver.GetGlobalInterceptors(); } ... }
  • 22.
    @ITCAMPRO #ITCAMP19Community Conferencefor 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 Conferencefor 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 Conferencefor 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 Conferencefor 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; } }
  • 26.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Create Development Patterns in Code
  • 27.
    @ITCAMPRO #ITCAMP19Community Conferencefor 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
  • 28.
    @ITCAMPRO #ITCAMP19Community Conferencefor IT Professionals Florin Coroș Author & Trainer, onCodeDesign onCodeDesign.com Co-founder & Partner, InfiniSwiss www.infiniswiss.com Co-founder & Partner, iQuarc www.iquarc.com email: florin@onCodeDesign.com blog: onCodeDesign.com tweet: @florincoros https://onCodeDesign.com/training