When projects do fail for reasons that are primary technical, the reason is often uncontrolled complexity. The complexity goes out of hand when the code lacks structure. In large software projects where many developers work on the same code base one of the biggest challenge is to get consistency in code, to create development patterns for common problems, so you can control the complexity and size of the system.
In this session I will show how we can achieve consistency through structure, rather then 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.
9. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 9
Cost of Change
Wrong approach consistently
repeated in all of the
application screens
vs
Uniquely new approach in all
of the application screens
10. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 10
Managing Complexity
when projects do fail for reasons that are primarily
technical, the reason is often
uncontrolled complexity
15. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 15
Quality through Structure
• You enforce consistency with structure
• Create a structure that makes difficult to write
bad code rather then code that follows the
design
• You use assemblies and references among
them to enforce rules
• Hide external frameworks to enforce the way
they are used
• Enforce Constructor Dependency Injection and
encourage Programming Against Interfaces
16. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 16
Assure Consistency in Using External Library by Hiding It
<<static class>>
Log
+LogError()
+LogWarining()
Exception Wrappers Decorators
<<Interface>>
API Interfaces
17. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 17
public interface ILog
{
void Info(string message, params object[] args);
void Warn(string message, params object[] args);
void Error(string message, Exception ex);
}
Enforces Separation of Concerns
• The application code only knows about ILog
• Once I is defined we can develop screens and call this
interface to log traces or errors
– The real implementation can be done later
• If logging needs changes, we can modify the interfaces
at once in all places it is used
– The implementation is in only one place, so one place to
change only
19. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 19
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
20. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 20
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);
}
...
}
21. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 21
Enforce Construction of Unit of Work
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;
}
}
22. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 22
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();
}
...
}
}
24. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 24
Enforce Separation of Data Access Concerns
<<Interface>>
TDataModel
IRepository
+GetEntities<TDataModel>()
<<Interface>>
TDataModel
IUnitOfWork
+GetEntities<TDataModel>()
+SaveChanges()
Database
Repository UnitOfWork
<<Stereotype>>
<<DTO>>
Order<<DTO>>
Person
25. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 25
DIP to Enforce Separation of Data Access Concern
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
IDbContextFactory
+CreateContext()
Database
<<DTO>>
Customer<<DTO>>
Order <<DTO>>
Person
DbContextFactoryUnitOfWork
Repository
26. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 26
DIP to Enforce OnSave/OnLoad Logic out of Data Access
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
IDbContextFactory
+CreateContext()
DbContextFactoryUnitOfWork
Repository
27. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 27
AppBoot as Support
<<Attribute>>
ServiceAttribute
IModule
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
IRepository
Database
<<DTO>>
Customer<<DTO>>
Order<<DTO>>
Person
DbContextFactoryUnitOfWork
Repository
<<Interface>>
TDataModel
IUnitOfWork
28. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 28
AppBoot to Enforce Constructor Dependency Injection
<<Attribute>>
ServiceAttribute
+ ServiceAttribute()
+ServiceAttribute(Type contract)
+ ServiceAttribute(Type t, Lifetime lifetime)
Bootstrapper
+Bootstrapper(Assembly[] assemblies)
+Run()
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
}
}
iQuarcAppBoot
29. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 29
Software systems should separate the startup process, when the application objects are constructed and the
dependencies are “wired” together, from the runtime logic that takes over after startup
<<Interface>>
IModule
+ Initialize()
Application
+ Initialize()
*
+ Modules[]
AppModule1
+ Initialize()
AppModule2
+ Initialize()
Enforce Separation of Construction from Use
public static void Main(string[] args)
{
var assemblies = GetApplicationAssemblies().ToArray();
Bootstrapper bootstrapper = new Bootstrapper(assemblies);
bootstrapper.ConfigureWithUnity();
bootstrapper.AddRegistrationBehavior(new ServiceRegistrationBehavior());
bootstrapper.Run();
}
30. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 30
Patters for Creating Services that Depend Only on Interfaces
[Service(typeof (IOrderingService))]
public 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)
{
...
}
}
31. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 31
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;
}
...
}
34. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 34
No References Between Modules -> Enforce Constraints
<<Attribute>>
ServiceAttribute
+ ServiceAttribute()
+ServiceAttribute(Type contract)
+ ServiceAttribute(Type t, Lifetime lifetime)
35. @ITCAMPRO #ITCAMP16Community Conference for IT Professionals 35
iQuarc iQuarc
Dependencies
Management
Design Patterns in
Service Composition
Enforce Consistency
through Structure
Enforce
Separation of Concerns
Patterns for
Queries & Commands
Interceptors for
Queries & Commands
Enforce Consistency through Application Infrastructure
iQuarcCode-Design-Training