writing maintainable software using solid principles

86
Writing Maintainable Software using SOLID Principles Doug Jones [email protected]

Upload: doug-jones

Post on 28-Jan-2018

101 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Writing Maintainable Software Using SOLID Principles

Writing Maintainable Softwareusing SOLID Principles

Doug Jones

[email protected]

Page 2: Writing Maintainable Software Using SOLID Principles

Have you ever played Jenga?

From <http://www.codemag.com/article/1001061>

Page 3: Writing Maintainable Software Using SOLID Principles

https://lostechies.com/derickbailey/2009/02/11/solid-development-

principles-in-motivational-pictures/

Page 4: Writing Maintainable Software Using SOLID Principles

MVC and Adding the Rails

MVC popularized by Ruby on Rails

If you consider a train on rails, the train goes where the rails take

it. - https://www.ruby-forum.com/topic/135143

by defining a convention and sticking to that you will find that your applications

are easy to generate and quick to get up and running. -

https://stackoverflow.com/questions/183462/what-does-it-mean-for-a-

programming-language-to-be-on-rails

Principles, Patterns, and Practices

Page 5: Writing Maintainable Software Using SOLID Principles

What’re we talking about?

Adding the rails with SOLID Principles and DRY

Shown using a particular methodology

Some patterns to help

Dependency Injection

…and how this leads to maintainable software

Page 6: Writing Maintainable Software Using SOLID Principles

The DRY Principle

DRY - Don’t Repeat Yourself!

“Every piece of knowledge must have a single, unambiguous, authoritative

representation within a system”

As opposed to WET

- The Pragmatic Programmer

Page 7: Writing Maintainable Software Using SOLID Principles

Copy/Paste Programmingpublic UserDetails GetUserDetails(int id)

{UserDetails userDetails;string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;var connection = new SqlConnection(cs);connection.Open();try{

userDetails = connection.Query<UserDetails>("select BusinessEntityID,EmailAddress from [Person].[EmailAddress] where BusinessEntityID = @Id",new { Id = id }).FirstOrDefault();

}finally{

connection.Dispose();}return userDetails;

}public UserContact GetUserContact(int id){

UserContact user = null;string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;var connection = new SqlConnection(cs);connection.Open();try{

user = connection.Query<UserContact>(@"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressIDFROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityIDINNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityIDwhere ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault();

}finally{

connection.Dispose();}return user;

}

Page 8: Writing Maintainable Software Using SOLID Principles

store.deviq.com/products/software-craftsmanship-

calendars-2017-digital-image-pack

Page 9: Writing Maintainable Software Using SOLID Principles

SOLID principles?

Principles of Object Oriented Design

Created by Robert C. Martin (known as Uncle Bob)

Also co-creator of the Agile Manifesto

Series of articles for The C++ Report (1996)

In his book Agile Software Development Principles, Patterns, and Practices

Page 10: Writing Maintainable Software Using SOLID Principles

The SOLID Principlesas in Robert C. Martin’s Agile Software Development book

SRP: Single Responsibility Principle

OCP: Open-Closed Principle

LSP: The Liskov Substitution Principle

DIP: The Dependency Inversion Principle

ISP: The Interface-Segregation Principle

Page 11: Writing Maintainable Software Using SOLID Principles

The SOLID Principlesas rearranged by Michael Feathers

SRP: Single Responsibility Principle

OCP: Open-Closed Principle

LSP: The Liskov Substitution Principle

ISP: The Interface-Segregation Principle

DIP: The Dependency Inversion Principle

Page 12: Writing Maintainable Software Using SOLID Principles

The SOLID Principlesrearranged by importance

SRP: Single Responsibility Principle

DIP: The Dependency Inversion Principle

OCP: Open-Closed Principle

LSP: The Liskov Substitution Principle

ISP: The Interface-Segregation Principle

- per Uncle Bob on Hanselminutes podcast

…and they’re about Managing Dependencies!

Page 13: Writing Maintainable Software Using SOLID Principles

SRP: Single Responsibility Principle

A class should have only one reason to change

SOLID

Page 14: Writing Maintainable Software Using SOLID Principles

https://lostechies.com/derickbailey/2009/02/11/solid-development-

principles-in-motivational-pictures/ SOLID

Page 15: Writing Maintainable Software Using SOLID Principles

public string SendUserEmail(int userId){

#region GetUserDataUserContact user = null;string cs = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;var connection = new SqlConnection(cs);connection.Open();try{

user = connection.Query<UserContact>(@"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressIDFROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityIDINNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityIDwhere ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault();

}finally{

connection.Dispose();}#endregion#region GetAdditionalInfoOnUservar httpClient = new HttpClient();string userCoordinatesJson = httpClient.GetStringAsync(

$"http://localhost:62032/api/geolocator/locateaddressid/{user.AddressId}").Result;var userCoordinates = JsonConvert.DeserializeObject<GeocodeCoordinates>(userCoordinatesJson);#endregion#region Business Logicif (IsNearby(userCoordinates)){

return "User is nearby. Do not send the scam email!";}#endregion#region BuildAndSendUserEmailSmtpClient client = new SmtpClient(Startup.Configuration.GetSection("Smtp:Host").Value);client.UseDefaultCredentials = false;client.Credentials = new NetworkCredential{

UserName = Startup.Configuration.GetSection("Smtp:UserName").Value,Password = Startup.Configuration.GetSection("Smtp:Password").Value

};MailMessage mailMessage = new MailMessage{

From = new MailAddress(Startup.Configuration.GetSection("Smtp:FromAddress").Value),

Subject = $"Congratulations, {user.FirstName}, you just won!",Body = "You won 1.3 million dollars! " +"There are just some taxes you need to pay on those winnings first. " +"Please send payment to [email protected].",

};mailMessage.To.Add(user.EmailAddress);client.Send(mailMessage);#endregionreturn "Email Sent! Just wait for the riches to come pouring in...";

} SOLID

Page 16: Writing Maintainable Software Using SOLID Principles

What could change?

Could have a SQL Server instance dns change [mitigated]

SQL table structure could change

Api url could change (and actually WILL, since pointing to localhost)

Api interface could change

Business rules could change

Now only send to users in countries with weak extradition laws

Email SMTP server could change, from address, user/pass [mitigated]

Subject and body of email could change

SOLID

Page 17: Writing Maintainable Software Using SOLID Principles

In other words, the following could

change…

Configuration data – server names, usernames, passwords

The way we get user data

The way we get user coordinates

The way we determine user eligibility

The way we contact users

SOLID

Page 18: Writing Maintainable Software Using SOLID Principles

public string SendUserEmail(int id){

var userRepository = new UserSqlRepository();var user = userRepository.GetUserContactById(id);#region GetAdditionalInfoOnUservar httpClient = new HttpClient();string userCoordinatesJson = httpClient.GetStringAsync(

$"http://localhost:62032/api/geolocator/locateaddressid/{user.AddressId}").Result;…

Refactor to UserRepository

public class UserSqlRepository{

private string _connectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;

public UserContact GetUserContactById(int userId){

UserContact user;using (var connection = GetOpenConnection()){

user = connection.Query<UserContact>(@"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressIDFROM Person.Person p INNER JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityIDINNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID = bea.BusinessEntityIDwhere ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault();

}return user;

}

SOLID

Page 19: Writing Maintainable Software Using SOLID Principles

After fully refactoring to adhere to SRP

public string SendUserEmail(int userId){

var userRepository = new UserSqlRepository();UserContact user = userRepository.GetUserContactById(userId);var userServiceClient = new UserServiceClient();GeocodeCoordinates userCoordinates =

userServiceClient.GetUserCoordinates(user.AddressId);var userScamEligibility = new NearbyUserScamEligibility();if (userScamEligibility.IsUserScamEligible(user, userCoordinates)){

return "Too risky. User not eligible for our scam.";}var messageSender = new SmtpUserMessageSender();messageSender.SendUserMessage(user);return "Email Sent! Just wait for the riches to come pouring in...";

}

SOLID

Page 20: Writing Maintainable Software Using SOLID Principles

DIP: Dependency Inversion Principle(not the DI: Dependency Injection)

A. High-level modules should not depend on low-level modules. Both should

depend on abstractions.

B. Abstractions should not depend on details. Details should depend on

abstractions.

- Uncle Bob

Separate implementations from abstractions

Forces us to code to abstractions/interfaces

Separate construction from use

Allow for much easier testing

Allow for much easier changes and therefore maintainability

SOLID

Page 21: Writing Maintainable Software Using SOLID Principles

store.deviq.com/products/software-craftsmanship-

calendars-2017-digital-image-pack SOLID

Page 22: Writing Maintainable Software Using SOLID Principles

Added the abstraction (interface)public interface IUserMessageSender

{void SendUserMessage(UserContact user);

}

------- SEPARATE FILE, ideally separate dll/package -------

public class SmtpUserMessageSender : IUserMessageSender{

private readonly string _smtpUserName = Startup.Configuration.GetSection("Smtp:UserName").Value;private readonly string _smtpPassword = Startup.Configuration.GetSection("Smtp:Password").Value;private readonly string _smtpFromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value;private readonly string _smtpHost = Startup.Configuration.GetSection("Smtp:Host").Value;public void SendUserMessage(UserContact user){

SmtpClient client = new SmtpClient(_smtpHost);client.UseDefaultCredentials = false;client.Credentials = new NetworkCredential{

UserName = _smtpUserName,Password = _smtpPassword

};MailMessage mailMessage = new MailMessage{

From = new MailAddress(_smtpFromAddress),Subject = $"Congratulations, {user.FirstName}, you just won!",Body = "You won 1.3 million dollars! " +"There are just some taxes you need to pay on those winnings first. " +"Please send payment to [email protected].",

};mailMessage.To.Add(user.EmailAddress);client.Send(mailMessage);

}

SOLID

Page 23: Writing Maintainable Software Using SOLID Principles

Inverted the dependencies

Expose dependencies via constructor

public class SmtpUserMessageSender : IUserMessageSender{

private readonly ISmtpUserMessageSenderConfig _config;public SmtpUserMessageSender(ISmtpUserMessageSenderConfig config){

_config = config;}public void SendUserMessage(UserContact user){

SmtpClient client = new SmtpClient(_config.Host);client.UseDefaultCredentials = false;client.Credentials = new NetworkCredential{

UserName = _config.UserName,Password = _config.Password

};MailMessage mailMessage = new MailMessage{

From = new MailAddress(_config.FromAddress),Subject = $"Congratulations, {user.FirstName}, you just won!",Body = "You won 1.3 million dollars! " +"There are just some taxes you need to pay on those winnings first. " +"Please send payment to [email protected].",

};mailMessage.To.Add(user.EmailAddress);client.Send(mailMessage);

}

SOLID

Page 24: Writing Maintainable Software Using SOLID Principles

Chuck Norris, Jon Skeet, and

Immutability (readonly keyword in C#)

Chuck Norris doesn’t read books…

Jon Skeet is immutable…

SOLID

Page 25: Writing Maintainable Software Using SOLID Principles

…using a config DTO class

public class SmtpMessageSenderConfig{

public string UserName { get; set; }public string Password { get; set; }public string Host { get; set; }public string FromAddress { get; set; }

}

SOLID

Page 26: Writing Maintainable Software Using SOLID Principles

Or if you want to be hardcore…

public class SmtpUserMessageSenderConfig : ISmtpUserMessageSenderConfig{

public string UserName { get; set; }public string Password { get; set; }public string Host { get; set; }public string FromAddress { get; set; }

}public interface ISmtpUserMessageSenderConfig{

string UserName { get; }string Password { get; }string Host { get; }string FromAddress { get; }

}

SOLID

Page 27: Writing Maintainable Software Using SOLID Principles

Client usage injecting smtp dependencies

public string SendUserEmail(int userId){

var userRepository = new UserSqlRepository();UserContact user = userRepository.GetUserContactById(userId);var userServiceClient = new UserServiceClient();GeocodeCoordinates userCoordinates =

userServiceClient.GetUserCoordinates(user.AddressId);var userScamEligibility = new NearbyUserScamEligibility();if (userScamEligibility.IsUserScamEligible(user, userCoordinates)){

return "Too risky. User not eligible for our scam.";}IUserMessageSender userMessageSender = new SmtpUserMessageSender(

new SmtpUserMessageSenderConfig{

Host = Startup.Configuration.GetSection("Smtp:Host").Value,UserName = Startup.Configuration.GetSection("Smtp:UserName").Value,Password = Startup.Configuration.GetSection("Smtp:Password").Value,FromAddress =

Startup.Configuration.GetSection("Smtp:FromAddress").Value});

userMessageSender.SendUserMessage(user);return "Scam Email Sent! Just wait for the riches to come pouring in...";

} SOLID

Page 28: Writing Maintainable Software Using SOLID Principles

Inject them all!

public string SendUserEmail(int userId){

var userRepository = new UserSqlRepository(new UserSqlRepositoryConfig{

ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value

});UserContact user = userRepository.GetUserContactById(userId);var userServiceClient = new UserServiceClient(new UserServiceClientConfig{

UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value},new HttpClientHandler());GeocodeCoordinates userCoordinates = userServiceClient.GetUserCoordinates(user.AddressId);var userScamEligibility = new NearbyUserScamEligibility();if (userScamEligibility.IsUserScamEligible(user, userCoordinates)){

return "Too risky. User not eligible for our scam.";}IUserMessageSender userMessageSender = new SmtpUserMessageSender(

new SmtpUserMessageSenderConfig{

Host = Startup.Configuration.GetSection("Smtp:Host").Value,UserName = Startup.Configuration.GetSection("Smtp:UserName").Value,Password = Startup.Configuration.GetSection("Smtp:Password").Value,FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value

});userMessageSender.SendUserMessage(user);return "Scam Email Sent! Just wait for the riches to come pouring in...";

} SOLID

Page 29: Writing Maintainable Software Using SOLID Principles

Strategy pattern in UserProcessor

public class UserProcessor : IUserProcessor{

private readonly IUserRepository _userRepository;private readonly IUserScamEligibility _userScamEligibility;private readonly IUserMessageSender _userMessageSender;public UserProcessor(IUserRepository userRepository, IUserScamEligibility

userScamEligibility, IUserMessageSender userMessageSender){

this._userRepository = userRepository;this._userScamEligibility = userScamEligibility;_userMessageSender = userMessageSender;

}public bool SendUserMessageByUserId(int userId){

var user = _userRepository.GetUserContactById(userId);if (!_userScamEligibility.IsUserScamEligible(user)){

return false;}_userMessageSender.SendUserMessage(user);return true;

}} SOLID

Page 30: Writing Maintainable Software Using SOLID Principles

The Strategy PatternDefine a family of algorithms, encapsulate each one, and make them interchangeable.

Strategy lets the algorithm vary independently from clients that use it. – GOF

This is achieved via composition and not via inheritance.

SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw

Page 31: Writing Maintainable Software Using SOLID Principles

Principles of reusable object-oriented design

Program to an interface, not an implementation.

Favor object composition over class inheritance.

Object composition has another effect on system design. Favoring object composition over class inheritance helps you keep each class encapsulated and focused on one task. Your classes and class hierarchies will remain small and will be less likely to grow into unmanageable monsters……our experience is that designers overuse inheritance as a reuse technique, and designs are often made more reusable (and simpler) by depending more on object composition.

GOF - Design Patterns: Elements of Reusable Object-Oriented Software (pp. 19-20). Pearson Education (1995).

SOLID

Page 32: Writing Maintainable Software Using SOLID Principles

Favor interface inheritance over

implementation inheritance

James Gosling, creator of Java, stated in 2001 interview that he was wrestling

with the idea of removing class inheritance:

“Rather than subclassing, just use pure interfaces. It's not so much that class

inheritance is particularly bad. It just has problems.”

https://www.javaworld.com/article/2073649/core-java/why-extends-is-

evil.html

[James Gosling on what he'd change in Java] explained that the real problem

wasn't classes per se, but rather implementation inheritance (the extends

relationship). Interface inheritance (the implements relationship) is preferable.

You should avoid implementation inheritance whenever possible.

- Allen Holub 2003

http://www.artima.com/intv/gosling34.html

SOLID

Page 33: Writing Maintainable Software Using SOLID Principles

Controller acting as Composition Root

public string SendUserEmail(int userId){

IUserRepository userRepository = new UserSqlRepository(new UserSqlRepositoryConfig{

ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value

});IUserServiceClient userServiceClient = new UserServiceClient(new UserServiceClientConfig{

UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value

},new HttpClientHandler());IUserScamEligibility userScamEligibility = new

NearbyUserScamEligibility(userServiceClient);IUserMessageSender userMessageSender = new SmtpUserMessageSender(

new SmtpUserMessageSenderConfig{

Host = Startup.Configuration.GetSection("Smtp:Host").Value,UserName = Startup.Configuration.GetSection("Smtp:UserName").Value,Password = Startup.Configuration.GetSection("Smtp:Password").Value,FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value

});IUserProcessorNoFactory userProcessor = new

UserProcessorNoFactory(userRepository,userScamEligibility,userMessageSender);userProcessor.SendUserMessageByUserId(userId);return "Scam Email Sent! Just wait for the riches to come pouring in...";

} SOLID

Page 34: Writing Maintainable Software Using SOLID Principles

Composition Root?

The main function in an application should have a concrete non-volatile side.

– Uncle Bob

The Composition Root is the place where we create and compose the objects

resulting in an object-graph that constitutes the application. This place should be

as close as possible to the entry point of the application.

From <http://www.dotnetcurry.com/patterns-practices/1285/clean-composition-roots-dependency-injection>

SOLID

Page 35: Writing Maintainable Software Using SOLID Principles

THE Composition Root

Dependency Injection via Startup.cs filepublic void ConfigureServices(IServiceCollection services)

{services.AddMvc();services.AddSingleton<IUserProcessor, UserProcessor>();services.AddSingleton<IUserRepository, UserRepository>();services.AddSingleton<IUserScamEligibility, UserScamEligibility>();services.AddSingleton<IUserServiceClient, UserServiceClient>();services.AddSingleton<IUserMessageSender, SmtpUserMessageSender>();services.AddSingleton<UserRepositoryConfig>(provider => new UserRepositoryConfig{

ConnectionString = _configuration.GetSection("ConnectionStrings:DefaultConnection").Value

});services.AddSingleton<UserServiceClientConfig>(provider => new UserServiceClientConfig{

UserDetailsUrl = _configuration.GetSection("UserApi:UserDetailsUrl").Value});services.AddSingleton<SmtpUserMessageSenderConfig>(provider => new

SmtpUserMessageSenderConfig{

Host = _configuration.GetSection("Smtp:Host").Value,UserName = _configuration.GetSection("Smtp:UserName").Value,Password = _configuration.GetSection("Smtp:Password").Value,FromAddress = _configuration.GetSection("Smtp:FromAddress").Value

});} SOLID

Page 36: Writing Maintainable Software Using SOLID Principles

Singletons

SOLID

Page 37: Writing Maintainable Software Using SOLID Principles

SingletonsEnsure exactly 1 instance

public sealed class Singleton{

private static readonly Singleton instance = new Singleton();

// Explicit static constructor to tell C# compiler// not to mark type as beforefieldinitstatic Singleton(){}

private Singleton(){}

public static Singleton Instance{

get{

return instance;}

}}

Jon Skeet - http://csharpindepth.com/articles/general/singleton.aspxSOLID

Page 38: Writing Maintainable Software Using SOLID Principles

Avoid Static Cling

A static member belongs to the type (class) and not to the instance.

It is a concrete implementation only and we cannot reference via abstraction.

Static Cling is a code smell used to describe the undesirable coupling

introduced by accessing static (global) functionality, either as variables or

methods. This coupling can make it difficult to test or modify the behavior of

software systems. - http://deviq.com/static-cling/

I tend to care when… I can’t unit test!

static includes external dependency.

MyNamespace.MyRepo.InsertInDb(…)

File.ReadAllText above

Static method includes non-deterministic behavior

DateTime.UtcNow();

System.IO.File.ReadAllText("SomeFile.txt");

SOLID

Page 39: Writing Maintainable Software Using SOLID Principles

Controller after DI Framework configured

[Route("api/[controller]")]public class ProcessorController : Controller{

private readonly IUserProcessor _userProcessor;public ProcessorController(IUserProcessor userProcessor){

_userProcessor = userProcessor;}// GET api/ProcessorController/sendusermessage/5[HttpGet("sendusermessage/{userId}")]public string SendUserMessage(int userId){

try{

if (_userProcessor.SendUserMessageByUserId(userId)){

return "Scam Message Sent! Just wait for the riches to come pouring in...";}return "Too risky. User not eligible for our scam.";

}catch(Exception ex){

return "Error occurred sending message. Exception message: " + ex.Message;}

}

SOLID

Page 40: Writing Maintainable Software Using SOLID Principles

Dependency Injection Frameworks

…also known as Inversion of Control (IoC)

C#

.NET Core Dependency Injection

Built into ASP.NET Core

A quick NuGet package away otherwise for NetStandard 2.0

Microsoft.Extensions.DependencyInjection

SimpleInjector

My preference for anything complicated or on .NET Framework

Highly opinionated

Ninject

Complex DI made easy

Not opinionated: will help you get it done regardless of pattern you choose

Java

Spring

SOLID

Page 41: Writing Maintainable Software Using SOLID Principles

DIP, DI, IoC…what’s that?

Dependency Inversion Principle (DIP)

A. High-level modules should not depend on low-level modules. Both should

depend on abstractions.

B. Abstractions should not depend on details. Details should depend on

abstractions.

Dependency Injection (DI)

Inversion of Control (IoC)

Hollywood Principle – “Don't call us, we'll call you”

Template Method

Events/Observable

IoC Container and DI Framework

Same thing!

SOLIDhttps://martinfowler.com/bliki/InversionOfControl.html

Page 42: Writing Maintainable Software Using SOLID Principles

OCP: The Open-Closed Principle

Software entities (classes, modules, functions, etc…) should be open for

extension, but closed for modification

We can extend client classes that use interfaces, but will need to change

code.

SOLID

Page 43: Writing Maintainable Software Using SOLID Principles

https://lostechies.com/derickbailey/2009/02/11/solid-development-

principles-in-motivational-pictures/ SOLID

Page 44: Writing Maintainable Software Using SOLID Principles

This cannot be extended

public class DIPMessageProcessorController: Controller{

[HttpGet("sendusermessage/{userId}")]public string SendUserEmail(int userId){

IUserRepository userRepository = new UserSqlRepository(new UserSqlRepositoryConfig{

ConnectionString = Startup.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value

});IUserServiceClient userServiceClient = new UserServiceClient(new UserServiceClientConfig{

UserCoordinatesUrl = Startup.Configuration.GetSection("UserApi:UserCoordinatesUrl").Value},new HttpClientHandler());IUserScamEligibility userScamEligibility = new NearbyUserScamEligibility(userServiceClient);IUserMessageSender userMessageSender = new SmtpUserMessageSender(

new SmtpUserMessageSenderConfig{

Host = Startup.Configuration.GetSection("Smtp:Host").Value,UserName = Startup.Configuration.GetSection("Smtp:UserName").Value,Password = Startup.Configuration.GetSection("Smtp:Password").Value,FromAddress = Startup.Configuration.GetSection("Smtp:FromAddress").Value

});IUserProcessorNoFactory userProcessor = new

UserProcessorNoFactory(userRepository,userScamEligibility,userMessageSender);userProcessor.SendUserMessageByUserId(userId);return "Scam Email Sent! Just wait for the riches to come pouring in...";

}

SOLID

Page 45: Writing Maintainable Software Using SOLID Principles

New is Glue

Any time you use the new keyword, you are gluing your code to a particular

implementation. You are permanently (short of editing, recompiling, and

redeploying) hard-coding your application to work with a particular class’s

implementation. - Steve Smith, https://ardalis.com/new-is-glue

SOLID

Page 46: Writing Maintainable Software Using SOLID Principles

This can be extended!

public class UserProcessor : IUserProcessor{

private readonly IUserRepository _userRepository;private readonly IUserScamEligibility _userScamEligibility;private readonly IUserMessageSender _userMessageSender;public UserProcessor(IUserRepository userRepository, IUserScamEligibility

userScamEligibility, IUserMessageSender userMessageSender){

this._userRepository = userRepository;this._userScamEligibility = userScamEligibility;_userMessageSender = userMessageSender;

}public bool SendUserMessageByUserId(int userId){

var user = _userRepository.GetUserContactById(userId);if (!_userScamEligibility.IsUserScamEligible(user)){

return false;}_userMessageSender.SendUserMessage(user);return true;

}}

SOLID

Page 47: Writing Maintainable Software Using SOLID Principles

Create another strategy for business rules

public class ExtraditionUserScamEligibility: IUserScamEligibility{

public bool IsUserScamEligible(UserContact user){

return !CountryLikelyToSeekExtradition(user.Country);}

SOLID

Page 48: Writing Maintainable Software Using SOLID Principles

Change strategy for UserMessageSender

public class TextUserMessageSender : IUserMessageSender{

public void SendUserMessage(UserContact user){

if (!IsMobilePhone(user.PhoneNumber)){

throw new SolidException("Cannot send text to non-mobile phone");}SendTextMessage(user.PhoneNumber);

}

SOLID

Page 49: Writing Maintainable Software Using SOLID Principles

Remember that Strategy Pattern…Define a family of algorithms, encapsulate each one, and make them interchangeable.

Strategy lets the algorithm vary independently from clients that use it. – GOF

This is achieved via composition and not via inheritance.

SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw

Page 50: Writing Maintainable Software Using SOLID Principles

Just change 2 lines of code…

public void ConfigureServices(IServiceCollection services){

services.AddMvc();services.AddSingleton<IUserScamEligibility, ExtraditionUserScamEligibility>();services.AddSingleton<IUserMessageSender, TextUserMessageSender>();

SOLID

Page 51: Writing Maintainable Software Using SOLID Principles

This has been EXTENDED

public class UserProcessor : IUserProcessor{

private readonly IUserRepository _userRepository;private readonly IUserScamEligibility _userScamEligibility;private readonly IUserMessageSender _userMessageSender;public UserProcessor(IUserRepository userRepository, IUserScamEligibility

userScamEligibility, IUserMessageSender userMessageSender){

this._userRepository = userRepository;this._userScamEligibility = userScamEligibility;_userMessageSender = userMessageSender;

}public bool SendUserMessageByUserId(int userId){

var user = _userRepository.GetUserContactById(userId);if (!_userScamEligibility.IsUserScamEligible(user)){

return false;}_userMessageSender.SendUserMessage(user);return true;

}}

SOLID

Page 52: Writing Maintainable Software Using SOLID Principles

Polymorphism

Changing the underlying implementation of the type to give it different

behavior

Different types of Polymorphism

Subtype Polymorphism (inheritance based), which can be from a supertype that is

Abstract class

Concrete class

Interface

Duck Typing

Dynamic languages like JavaScript and Ruby

If it has the Quack() function, I can call it regardless of the type

Parametric Polymorphism

Generics with parameter polymorphism

List<T> open type with closed type as List<string>

Ad hoc Polymorphism

Method overloading

SOLID

Page 53: Writing Maintainable Software Using SOLID Principles

The UPS as a Decorator

Mark Seemann. Dependency Injection in .NET

Page 54: Writing Maintainable Software Using SOLID Principles

Extend a strategy with a Decorator

public class CacheDecoratorUserRepository : IUserRepository{

private readonly IUserRepository _userRepository;private readonly TimeSpan _timeToLive;private readonly ConnectionMultiplexer _redis;public CacheDecoratorUserRepository(IUserRepository userRepository,

CacheDecoratorUserRepositoryConfig config){

_userRepository = userRepository;_timeToLive = config.TimeToLive;_redis = ConnectionMultiplexer.Connect(config.ConfigurationString);

}public UserContact GetUserContactById(int userId){

//this would have multiple try/catch blocksIDatabase db = _redis.GetDatabase();string key = $"SolidCore:GetUserContactById:{userId}";string redisValue = db.StringGet(key);if (redisValue != null){

return JsonConvert.DeserializeObject<UserContact>(redisValue);}var user = _userRepository.GetUserContactById(userId);if (user == null){

return null;}string serializedValue = JsonConvert.SerializeObject(user);db.StringSet(key, serializedValue,_timeToLive);return user;

}} SOLID

Page 55: Writing Maintainable Software Using SOLID Principles

The Decorator Pattern

Attach additional responsibilities to an object dynamically. Decorators provide a

flexible alternative to subclassing for extending functionality. - GoF

SOLIDUML Diagrams - https://yuml.me/diagram/scruffy/class/draw

Page 56: Writing Maintainable Software Using SOLID Principles

.NET Core DI doesn’t directly support Decorator

services.AddSingleton<UserSqlRepository>();services.AddSingleton<CacheDecoratorUserRepositoryConfig>(provider =>

new CacheDecoratorUserRepositoryConfig{

ConfigurationString = _configuration.GetSection("Redis:ConfigurationString").Value,

TimeToLive = TimeSpan.Parse(_configuration.GetSection("Redis:TimeToLive").Value)

});services.AddSingleton<IUserRepository>(provider =>{

var userRepository = provider.GetService<UserSqlRepository>();var config = provider.GetService<CacheDecoratorUserRepositoryConfig>();return new CacheDecoratorUserRepository(userRepository, config);

});

SOLID

Page 57: Writing Maintainable Software Using SOLID Principles

…you now have new functionality here!

public class UserProcessorNoFactory : IUserProcessorNoFactory{

private readonly IUserRepository _userRepository;private readonly IUserScamEligibility _userScamEligibility;private readonly IUserMessageSender _userMessageSender;public UserProcessorNoFactory(IUserRepository userRepository,

IUserScamEligibility userScamEligibility, IUserMessageSender userMessageSender){

this._userRepository = userRepository;this._userScamEligibility = userScamEligibility;_userMessageSender = userMessageSender;

}public bool SendUserMessageByUserId(int userId){

var user = _userRepository.GetUserContactById(userId);if (!_userScamEligibility.IsUserScamEligible(user)){

return false;}_userMessageSender.SendUserMessage(user);return true;

}}

SOLID

Page 58: Writing Maintainable Software Using SOLID Principles

Dependency with short lifetime

public class SqlDbConnectionFactory : IDbConnectionFactory{

private readonly SqlDbConnectionFactoryConfig _config;

public SqlDbConnectionFactory(SqlDbConnectionFactoryConfig config){

_config = config;}public IDbConnection GetOpenConnection(){

var connection = new SqlConnection(_config.ConnectionString);connection.Open();return connection;

}}

SOLID

Page 59: Writing Maintainable Software Using SOLID Principles

Client of DBConnectionFactory

public class UserSqlRepository : IUserRepository{

private readonly IDbConnectionFactory _dbConnectionFactory;

public UserSqlRepository(IDbConnectionFactory dbConnectionFactory){

_dbConnectionFactory = dbConnectionFactory;}public UserContact GetUserContactById(int userId){

UserContact userContact;using (var connection = _dbConnectionFactory.GetOpenConnection()){

userContact = connection.Query<UserContact>(@"SELECT p.FirstName, bea.BusinessEntityID,ea.EmailAddress,bea.AddressIDFROM Person.Person p INNER JOIN Person.EmailAddress ea ON

p.BusinessEntityID = ea.BusinessEntityIDINNER JOIN Person.BusinessEntityAddress bea ON ea.BusinessEntityID =

bea.BusinessEntityIDwhere ea.BusinessEntityID = @Id", new { Id = userId }).FirstOrDefault();

}return userContact;

}

Page 60: Writing Maintainable Software Using SOLID Principles

New instance every call

using Abstract Factory

public class Sha512HashAlgorithmFactory : IHashAlgorithmFactory{

public HashAlgorithm GetInstance(){

return System.Security.Cryptography.SHA512.Create();}

}

SOLID

Page 61: Writing Maintainable Software Using SOLID Principles

Abstract Factory to choose strategy

public class UserMessageSenderFactory : IUserMessageSenderFactory{

private readonly IEnumerable<IUserMessageSender> _userMessageSenders;

public UserMessageSenderFactory(IEnumerable<IUserMessageSender> userMessageSenders){

_userMessageSenders = userMessageSenders;}

public IUserMessageSender GetInstance(string messageType){

var userMessageSender = _userMessageSenders.FirstOrDefault(x =>x.MessageType.Equals(messageType, StringComparison.OrdinalIgnoreCase));

if (userMessageSender == null){

throw new SolidException("Invalid Message Type");}return userMessageSender;

}}

SOLID

Page 62: Writing Maintainable Software Using SOLID Principles

DI just needed additional registrations…

services.AddSingleton<IUserMessageSender,SmtpUserMessageSender>();services.AddSingleton<IUserMessageSender, TextUserMessageSender>();services.AddSingleton<IUserMessageSenderFactory, UserMessageSenderFactory>();

SOLID

Page 63: Writing Maintainable Software Using SOLID Principles

Change strategies based on input

public class UserProcessor : IUserProcessor{

private readonly IUserRepository _userRepository;private readonly IUserScamEligibility _userScamEligibility;private readonly IUserMessageSenderFactory _userMessageSenderFactory;public UserProcessor(IUserRepository userRepository, IUserScamEligibility

userScamEligibility, IUserMessageSenderFactory userMessageSenderFactory){

this._userRepository = userRepository;this._userScamEligibility = userScamEligibility;this._userMessageSenderFactory = userMessageSenderFactory;

}public bool SendUserMessageByUserId(int userId, string messageType){

var userMessageSender = _userMessageSenderFactory.GetInstance(messageType);var user = _userRepository.GetUserContactById(userId);if (!_userScamEligibility.IsUserScamEligible(user)){

return false;}userMessageSender.SendUserMessage(user);return true;

}}

SOLID

Page 64: Writing Maintainable Software Using SOLID Principles

Abstract Factory

Provide an interface for creating families of related or dependent objects without

specifying their concrete classes. – GoF

It offers a good alternative to the complete transfer of control that’s involved in full

INVERSION OF CONTROL, because it partially allows the consumer to control the

lifetime of the DEPENDENCIES created by the factory; the factory still controls what is

being created and how creation happens.

- Mark Seemann. Dependency Injection in .NET (Kindle Locations 3628-3630). Manning

Publications.

SOLID

Page 65: Writing Maintainable Software Using SOLID Principles

Don’t go overboard

Strategically choose what changes to close design against.

Resisting premature abstraction is as important as abstraction itself.

Over-conformance to the principles leads to the design smell of Needless

Complexity.

No significant program can be 100% closed.

- Uncle Bob, taken from books, articles, podcasts

I had a problem, so I tried to solve it with Java…

SOLID

Page 66: Writing Maintainable Software Using SOLID Principles

LSP: Liskov Substitution Principle

The LSP can be paraphrased as follows:

Subtypes must be substitutable for their base types

Can’t add unexpected exceptions

Contravariance of method arguments in the subtype

Covariance of return types in the subtype

Preconditions cannot be strengthened in a subtype

Postconditions cannot be weakened in a subtype.

Invariants of the supertype must be preserved in a subtype.

History constraint

…which we’ll use to also mean implementations of interfaces must be

substitutable for other implementations

SOLID

Page 67: Writing Maintainable Software Using SOLID Principles

store.deviq.com/products/software-craftsmanship-

calendars-2017-digital-image-pack SOLID

Page 68: Writing Maintainable Software Using SOLID Principles

I have this Serializer

public interface ISerializer{

T DeserializeObject<T>(string value);string SerializeObject(object value);

}

public class JsonSerializer : ISerializer{

public T DeserializeObject<T>(string value){

return JsonConvert.DeserializeObject<T>(value);}public string SerializeObject(object value){

return JsonConvert.SerializeObject(value);}

}

SOLID

Page 69: Writing Maintainable Software Using SOLID Principles

but I want to Serialize Binary

public class BinarySerializer : ISerializer{

public string SerializeObject(object value){

using (var stream = new MemoryStream()){

var formatter = new BinaryFormatter();formatter.Serialize(stream, value);stream.Flush();stream.Position = 0;return Convert.ToBase64String(stream.ToArray());

}}public T DeserializeObject<T>(string value){

throw new NotImplementedException();}

}SOLID

Page 70: Writing Maintainable Software Using SOLID Principles

My LSP violation caused OCP violation!

public class DeserializeMaybe{

private readonly ISerializer _serializer;

public DeserializeMaybe(ISerializer serializer){

_serializer = serializer;}public User.User DeserializeUser(string serializedUser){

if(_serializer is JsonSerializer){

return _serializer.DeserializeObject<User.User>(serializedUser);}return null;

}}

SOLID

Page 71: Writing Maintainable Software Using SOLID Principles

…and when I try to serialize my User…

another LSP violation

private string SerializeUser(User user){

return _serializer.SerializeObject(user);}

SOLID

Page 72: Writing Maintainable Software Using SOLID Principles

ISP: Interface Segregation Principle

The ISP:

Clients should not be forced to depend on methods that they do not use.

Clients should not know about objects as a single class with a noncohesive interface.

SOLID

Page 73: Writing Maintainable Software Using SOLID Principles

store.deviq.com/products/software-craftsmanship-

calendars-2017-digital-image-pack SOLID

Page 74: Writing Maintainable Software Using SOLID Principles

Interface with multiple roles

public interface IMultiRoleUserRepository{

UserContact GetUserContactById(int userId);void InsertUser(User user);void UpdateEmailAddress(int userId, string emailAddress);

}

SOLID

Page 75: Writing Maintainable Software Using SOLID Principles

Split the interface to single roles

public interface IUserContactReaderRepository{

UserContact GetUserContactById(int userId);}

public interface IUserWriterRepository{

void InsertUser(User user);void UpdateEmailAddress(int userId, string emailAddress);

}

SOLID

Page 76: Writing Maintainable Software Using SOLID Principles

Repository implementing multiple roles

public class MultiRoleUserSqlRepository : IUserContactReaderRepository, IUserWriterRepository

{private readonly IDbConnectionFactory _dbConnectionFactory;public MultiRoleUserSqlRepository(IDbConnectionFactory dbConnectionFactory){

_dbConnectionFactory = dbConnectionFactory;}public UserContact GetUserContactById(int userId){

UserContact userContact;using (var connection = _dbConnectionFactory.GetOpenConnection()){

userContact = ...}return userContact;

}public void InsertUser(User user){

...}public void UpdateEmailAddress(int userId, string emailAddress){

...}

SOLID

Page 77: Writing Maintainable Software Using SOLID Principles

Pure Functions – the ideal

The function always evaluates the same result value given the same argument

value(s). The function result value cannot depend on any hidden information

or state that may change while program execution proceeds or between

different executions of the program, nor can it depend on any external input

from I/O devices (usually—see below).

Evaluation of the result does not cause any semantically observable side

effect or output, such as mutation of mutable objects or output to I/O

devices (usually—see below).

https://en.wikipedia.org/wiki/Pure_function

Page 78: Writing Maintainable Software Using SOLID Principles

Agile Manifesto

Individuals and interactions over processes and tools

Working software over comprehensive documentation

Customer collaboration over contract negotiation

Responding to change over following a plan

That is, while there is value in the items on the right, we value the items on the

left more.

Page 79: Writing Maintainable Software Using SOLID Principles

A SOLID Manifesto

Composition over inheritance

Interface inheritance over implementation inheritance

Classes with state OR behavior over classes with state AND behavior

Singletons over multiple instances or statics

Volatile state scoped to functions over volatile state scoped to classes

That is, while there is value in the items on the right, I value the items on the

left more.

Page 80: Writing Maintainable Software Using SOLID Principles

So what’s the downside?

Class and Interface Explosion

Complexity (increase in one kind of complexity)

I have an instance of the IService interface. What implementation am I using?

Constructor injection spreads like a virus

Page 81: Writing Maintainable Software Using SOLID Principles

…but you now have maintainable

software using SOLID principles!

Testable!

What parts should you test?

What is Michael Feather’s definition of legacy code?

Easy to change

Particularly at the abstractions (the “seams” - Seemann)

Code can be reused

Design easy to preserve

Forces cognizance of dependencies

Requires much smaller mental model

Small functions with method injection isolated from outside world requires MUCH

SMALLER mental model of code. Complexity greatly reduced. Instead of having to

understand system, you can just understand THAT function. - Mark Seemann on

.NET Rocks podcast

Page 82: Writing Maintainable Software Using SOLID Principles

https://lostechies.com/derickbailey/2009/02/11/solid-development-

principles-in-motivational-pictures/

We’ve turned our Jenga game…

Page 83: Writing Maintainable Software Using SOLID Principles

…into a well built structure that’s easy

to maintain

Page 84: Writing Maintainable Software Using SOLID Principles

Appendix The Pragmatic Programmer – Andrew Hunt and David Thomas

SOLID Jenga reference - http://www.codemag.com/article/1001061

Images noted from Steve Smith’s Software Craftsmanship Calendars - store.deviq.com/products/software-

craftsmanship-calendars-2017-digital-image-pack – used with express written permission

Composition root definition - http://www.dotnetcurry.com/patterns-practices/1285/clean-composition-roots-

dependency-injection

SOLID Motivational Pictures from Los Techies - https://lostechies.com/derickbailey/2009/02/11/solid-development-

principles-in-motivational-pictures/

Microsoft .NET Application Architecture - Architecting Modern Web Applications with ASP.NET Core and Azure

Mark Seemann. Dependency Injection in .NET

Feathers, Michael - Working Effectively With Legacy Code

UML Diagrams - https://yuml.me/diagram/scruffy/class/draw

IoC Definition - https://martinfowler.com/bliki/InversionOfControl.html

Pure Function - https://en.wikipedia.org/wiki/Pure_function

IoC Explained - https://martinfowler.com/bliki/InversionOfControl.html

New is Glue - https://ardalis.com/new-is-glue

Static Cling - http://deviq.com/static-cling/

6 ways to make a singleton - http://csharpindepth.com/articles/general/singleton.aspx

GoF - Design Patterns: Elements of Reusable Object-Oriented Software

Page 85: Writing Maintainable Software Using SOLID Principles

Writing Maintainable Softwareusing SOLID Principles

Doug Jones

[email protected]

Questions?

Page 86: Writing Maintainable Software Using SOLID Principles

Project layout