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.
13. https://onCodeDesign.com/CraftConf2018
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
13
15. https://onCodeDesign.com/CraftConf2018
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
15
22. https://onCodeDesign.com/CraftConf2018
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
22
23. https://onCodeDesign.com/CraftConf2018
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
23
24. https://onCodeDesign.com/CraftConf2018
DIP to Move OnSave & OnLoad Logic out of Data Access
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
IDbContextFactory
+CreateContext()
DbContextFactoryUnitOfWork
Repository
24
25. https://onCodeDesign.com/CraftConf2018
AppBoot: DI Abstractions & Type Descovery
<<Attribute>>
ServiceAttribute
IModule
<<Interface>>
TDataModel
IEntityInterceptor
+OnLoad()
+OnSaving()
<<Interface>>
TDataModel
IRepository
Database
<<DTO>>
Customer<<DTO>>
Order<<DTO>>
Person
DbContextFactoryUnitOfWork
Repository
<<Interface>>
TDataModel
IUnitOfWork
25
26. https://onCodeDesign.com/CraftConf2018
AppBoot Enforces 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
26
27. https://onCodeDesign.com/CraftConf2018
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();
}
27
28. https://onCodeDesign.com/CraftConf2018
Patters forCreating Services thatDepend Only onInterfaces
[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)
{
...
}
}
28
29. https://onCodeDesign.com/CraftConf2018
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;
}
...
}
29
My name is Florin Coros
I live in Cluj-Napoca
I like to go to ROCK concerts
I like to travel with my girl-friend
I like to play GO. GO is a game that thought me to look a few steps ahead, which turned to be very useful for me…
I tweet quite a lot
I am a big fan of Uncle Bob
I am a Software Architect at ISDC
I am one of the cofounders of RABS
I am part of iQuarc
4’
How many of you work in projects with more people?
Or are in the position of reading & modifying code written by others?
How many of you work alone?
One solution to a common problem
(also give DI example, using new instead of DI)
Consistent way to do:
Data Access
Dependency Injection
…
Logging
Error Handling
Localization
Authorization
Drawbacks:
High Cost of Change
Poor SoC
Time to add new features is bigger -> less efficient
Error prone
9’
He will not know which path to follow. He will not know which one is the one.
Say the story ab DCV increase complexity increase accidental complexity
When you see that in some places we use EF, in others ADO, in others stored procedures, you don’t know why and which one is the good one
If consistency you can have one strategy, one solution with which you can fix or replace all at onece!
- Its like being able to use an ReplaceALL function!
13’
We want our code to look like the one on the right!
We want to be able to identify patterns on how our code is written
I don’t necessary mean the known Design Patterns, but patterns specific to our project, Patterns that clearly say how we do things in this project, how this concern is addressed or implemented
A system with patterns is going to have a manageable complexity.
It is maintainable even if it is very complex and large is size
Patterns make it simple
A system in which everything is unique is going to be difficult to change and maintain.
Even if it is small and simple, the uniqness, the chaos makes it complex
17’
Too often “Quick and Dirty” wins over “The only way to go fast, is to go well”
Do you know who says this?
Yes. Uncle Bob! The initiator of the Craftsmanship movement
He created this conscience that we should write clean code, because we are good developers, because we are good professionals that care ab what we are doing and about code quality
This is nice but it is not enough
Especially when we are under the pressure of delivery
When we are encouraged to go on the Quick & Dirty death path, under the argument of being pragmatic
People are going to forget these rules when they are under pressure to deliver one screen a day. Fast!
Its simple to forget!
Imagine the following context: 3 years estimate -> 1 year delivery
So what do we do? What do we do to get a minimum level of quality, that can sustain the project over time?
You have some people that review the code and check if rules are followed!
Two guys who are going to review everyone’s code to check if we have the best practices followed, if we have a good SoC, if arch is followed, and if there are places where significant improvements could be done by applying Design Patterns / SOLID Principles or other principles…
22’
no circular dependencies – achieved through
You put in a structure through your design in a way that it is more difficult to write bad code than code which follows your design
You put in your design the constraints.
These may make your design to be more closed, less generic, but these are compromises worth doing to achieve consistency through structure
25’
- Enforces Separation of Concern!
- Libraries can be used for any kind of applications. We are not interested in building any kid of application, we are interested in building THIS specific application
End at 28’
Start at 28’
Encapsulates ALL data access concerns
Assures a consistent way to do data access
any component that has to access data has to use a IRepository or an IUnitOfWork
Constraints SoC: DA from BLL
There is no other way to get an implementation of the IRepository than DI
- IRepository used to get data for read-only
- Light implementation that can only be obtained through DI
- Returns IQueriable<T>
- Central place for all queries to the database
Testable code
No need to pass IRepository as parameters!
The UnitOfWork can only be created through this factory method on the repository
The UnitOfWork is in an using statement. It has a very well defined scope
It is not to be passed as parameters to methods
34’
We could even take out from the VS Solution the DbContextProvider project if the Context.tt generate is triggered by the SalesEntities.tt and a post build event of the DataModel assembly also builds the DbContextProvider.csproj
43’
Enforces Dependency Injection through Constructor only
- By hiding the underlying DI Container it makes that the configuration of it can ONLY be done at app startup through annotations or conventions
- Enforces Dependency Injection through Constructor only
- By hiding the underlying DI Container it makes that the configuration of it can ONLY be done at app startup through annotations or conventions
47’
Dependencies are visible in the constructor
Dependencies are on interfaces ONLY Loose Coupled code testable code
Dependencies are visible, so we can see classes that have too many dependencies –> poor cohesion refactor to loose coupling
The Implementation depends on INTERFACES of other services it uses and on DTOs which are paramenters
50’
52’
51’
A question I always get
- Technical / strategic debt can be done bellow the line
No Design, no trade-off:
no matter how less attention I will pay to the design I will still go slow. I am impeded by the mess.
Less quality -> less options -> more unexpected things -> slower you are
Design: I can make the trade-off
- Red team productivity NEVER decreases. See above chart with productivity going to 0.
- It is important to know where you are. As long as you are under the line you may take technical debt but you must be aware! To be able to keep it under control
- When not pay attention to design:
- Prj finishes before the intersection point: prototype, throw away, PoC
- CHALLENGE: unaware of having bad code or taking too big technical debt.
It takes us by surprise
- By being on the blue line with our prj most of the times we have done a great job in making our customers/managers not to trust us!
- When does quality pay off?
All projects have a slower start and then the efficiency increases. The project reaches an healthy productivity rate after the design payoff line
Interesting aspects about intersection point
Prj finishes before the intersection point: prototype, throw away, PoC
Technical / strategic debt can be done bellow.
- Cannot be done above: even if you pay less attention/effort on quality you will still need TOO much time to add a feature and the quality still drops
- less quality slower you are
Above the line we were in the initial example, but without knowing it!
CHALLENGE: unaware of having bad code or taking too big technical debt.
It takes us by surprise
I will show how by doing GOOD unit tests you can keep this under control by having indicators of when your quality drops
By being on the blue line with our prj most of the times we have done a great job in making our customers/managers not to trust us!
It is important to know where you are. As long as you are under the line you may take technical debt but you must be aware! To be able to keep it under control
Good UT can help us knowing when our quality drops and we sleep over the line
All projects have a slower start and then the efficiency increases. The project reaches an healthy productivity rate after the design payoff line