SlideShare a Scribd company logo
1 of 73
Download to read offline
Speaker: Alexey Golub @Tyrrrz
Dependency absolution
Application as a pipeline
Who am I?
Speaker: Alexey Golub @Tyrrrz
Open source developer
• https://github.com/Tyrrrz
• 6 active projects
• 4000 total stars
• 400k total downloads
Senior software developer
• Svitla Systems, Inc
• C#, ASP.NET Core, Docker, AWS
Interests
• C#, F#, JavaScript
• Functional programming
• Language design, parsing, DSLs
• Product design, photography
Best code is no code at all
Bad design decisions lead to writing more code in the long run
Speaker: Alexey Golub @Tyrrrz
Domain modeling
Speaker: Alexey Golub @Tyrrrz
Object-oriented approach
Speaker: Alexey Golub @Tyrrrz
public class Ledger
{
public Guid Id { get; }
public decimal Balance { get; private set; }
public Ledger(Guid id, decimal initialBalance) { /* ... */ }
public void Debit(decimal amount) { /* ... */ }
public void Credit(decimal amount) { /* ... */ }
public static Ledger Create(decimal initialBalance = 0.0M) { /* ... */ }
public static Ledger Retrieve(Guid id) { /* ... */ }
}
public void Debit(decimal amount)
{
if (Balance < amount)
throw new InsufficientFundsException();
Balance -= amount;
Database.Save(this);
}
Speaker: Alexey Golub @Tyrrrz
Business logic
Dependency
Speaker: Alexey Golub @Tyrrrz
Object
Dependency
Speaker: Alexey Golub @Tyrrrz
Object
Dependency
Object
Abstraction
Dependency
Mock
public void Debit(decimal amount)
{
if (Balance < amount)
throw new InsufficientFundsException();
Balance -= amount;
ServiceLocator.GetService<IDatabase>().Save(this);
}
Speaker: Alexey Golub @Tyrrrz
public void Debit(decimal amount)
{
if (Balance < amount)
throw new InsufficientFundsException();
Balance -= amount;
ServiceLocator.GetService<IDatabase>().Save(this);
}
private readonly IDatabase _database;
/* ... */
public void Debit(decimal amount)
{
if (Balance < amount)
throw new InsufficientFundsException();
Balance -= amount;
_database.Save(this);
}
Speaker: Alexey Golub @Tyrrrz
Abstract dependency
Benefits of dependency injection
• Inversion of control
• Lifetime management
• Decoupled implementations
• Reduced module complexity
• Isolated testing
Speaker: Alexey Golub @Tyrrrz
Layered architecture
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Domain models
Data access
Services
System boundary
Layered/onion architecture
public class Ledger
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
}
public interface ILedgerRepository
{
Task AddAsync(Ledger ledger);
Task UpdateAsync(Ledger ledger);
Task<Ledger> RetrieveAsync(Guid id);
}
public interface ILedgerService
{
Task<Ledger> CreateAsync(decimal initialBalance = 0.0M);
Task<Ledger> RetrieveAsync(Guid id);
Task DebitAsync(Guid id, decimal amount);
Task CreditAsync(Guid id, decimal amount);
}
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Ledger
LedgerRepository
LedgerService
LedgerController
System boundary (entry point)
Service layer
Data access layer
Domain model
Speaker: Alexey Golub @Tyrrrz
Ledger
LedgerRepository
LedgerService
LedgerController
TransactionFee Counterparty
TransactionController
TransactionService
FeeService
Statement
StatementService
CounterpartyService
FeeRepository TransactionRepositoryCounterpartyRepository StatementRepository
ScheduledJob
NotificationService
Layers of issues
Speaker: Alexey Golub @Tyrrrz
Hard to combine with OOD
• Existing paradigms became anti-patterns
• Encapsulation is gone
• Inheritance is disfavored
• Static classes & methods are “untestable”
Speaker: Alexey Golub @Tyrrrz
Causational indirectness
• Hard to trace
• Hard to reason about
• Implicit dependencies & DI made us lazy
Speaker: Alexey Golub @Tyrrrz
Leaky async
• Async stems from IO side-effects and leaks into business logic
• Unnecessary state machines impact performance
• Code becomes redundantly verbose
Speaker: Alexey Golub @Tyrrrz
public class LedgerService : ILedgerService
{
private readonly ILedgerRepository _repository;
/* ... */
public async Task DebitAsync(Guid ledgerId, decimal amount)
{
var ledger = await _repository.GetByIdAsync(ledgerId);
if (ledger is null)
throw new EntityNotFoundException();
if (ledger.Balance < amount)
throw new InsufficientFundsException();
ledger.Balance -= amount;
await _repository.SaveChangesAsync();
}
}
Speaker: Alexey Golub @Tyrrrz
Leaky async
public interface ILedgerService
{
Task<Ledger> CreateAsync(decimal initialBalance = 0.0M);
Task<Ledger> RetrieveAsync(Guid id);
Task DebitAsync(Guid id, decimal amount);
Task CreditAsync(Guid id, decimal amount);
}
Speaker: Alexey Golub @Tyrrrz
ILedgerService reveals the fact that LedgerService depends on ILedgerRepository
Obscured complexity
• Module complexity is reduced
• Total complexity is increased
• Assumptions between communicating modules
Speaker: Alexey Golub @Tyrrrz
Mock-based testing
• Implementation-aware
• Very brittle
• Massive time sink
Speaker: Alexey Golub @Tyrrrz
// Arrange
var transactionRepositoryMock = new Mock<ITransactionRepository>();
transactionRepositoryMock.Setup(x => x.GetAll())
.Returns(testData.AsQueryable());
var counterpartyServiceMock = new Mock<ICounterpartyService>();
counterpartyServiceMock.Setup(x => x.GetCounterpartyAsync(It.IsAny<Transaction>()))
.ReturnsAsync(testCounterparty);
var transactionService = new TransactionService(
transactionRepositoryMock.Object,
counterpartyServiceMock.Object
);
var transaction = new Transaction
{
// ...
}
// Act
await transactionService.ExecuteTransactionAsync(transaction);
// Assert
// ...
Speaker: Alexey Golub @Tyrrrz
Implementation-aware
Autotelic abstractions
• Every object requires an explicit abstraction
• Abstractions are needed for the sole purpose of mocking
• Abstractions don’t try to encapsulate behavior
• Abstractions are owned by implementations instead of consumers
Speaker: Alexey Golub @Tyrrrz
Abstraction is a great tool
and a terrible goal
Speaker: Alexey Golub @Tyrrrz
Is OOP the wrong tool for
the job?
Speaker: Alexey Golub @Tyrrrz
Functional architecture
Speaker: Alexey Golub @Tyrrrz
ImpurePure
Speaker: Alexey Golub @Tyrrrz
Side-effects
Data
transformation
Speaker: Alexey Golub @Tyrrrz
Validating, parsing,
mapping, ordering,
filtering, projecting…
Reading request, writing
to DB, enqueuing
messages…
ImpurePure
Speaker: Alexey Golub @Tyrrrz
ImpurePure
Speaker: Alexey Golub @Tyrrrz
Pure functions
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
F(x)Data in Data out
Isolation
SQL
Stateless
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
var fees = _invoiceService.CalculateFees();
Can I make any assumptions about _invoiceService?
Speaker: Alexey Golub @Tyrrrz
var fees = InvoiceLogic.CalculateFees(ledger, invoiceDate);
Dependencies are clear which makes the intent clear
Data-driven
Speaker: Alexey Golub @Tyrrrz
private readonly CommonDbContext _dbContext;
/* ... */
public IEnumerable<Transaction> GetCompletedTransactions(Guid ledgerId)
{
return _dbContext.Transactions
.Where(t => t.LedgerId == ledgerId)
.Where(t => t.IsCompleted);
}
Speaker: Alexey Golub @Tyrrrz
Data container
Speaker: Alexey Golub @Tyrrrz
public static IEnumerable<Transaction> GetCompletedTransactions(
IEnumerable<Transaction> allTransactions, Guid ledgerId)
{
return allTransactions
.Where(t => t.LedgerId == ledgerId)
.Where(t => t.IsCompleted);
}
Deterministic
Speaker: Alexey Golub @Tyrrrz
public static DateTimeOffset GetInvoiceDate(int day)
{
var instant = DateTimeOffset.Now;
var potentialInvoiceDate = instant.Day >= day
? instant.AddDays(day - instant.Day)
: instant.AddMonths(-1).AddDays(day - instant.Day);
if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Saturday)
potentialInvoiceDate = potentialInvoiceDate.AddDays(-1);
else if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Sunday)
potentialInvoiceDate = potentialInvoiceDate.AddDays(-2);
return potentialInvoiceDate;
}
Non-deterministic
Speaker: Alexey Golub @Tyrrrz
public static DateTimeOffset GetInvoiceDate(DateTimeOffset instant, int day)
{
var potentialInvoiceDate = instant.Day >= day
? instant.AddDays(day - instant.Day)
: instant.AddMonths(-1).AddDays(day - instant.Day);
if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Saturday)
potentialInvoiceDate = potentialInvoiceDate.AddDays(-1);
else if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Sunday)
potentialInvoiceDate = potentialInvoiceDate.AddDays(-2);
return potentialInvoiceDate;
}
Speaker: Alexey Golub @Tyrrrz
Inherently testable
Speaker: Alexey Golub @Tyrrrz
var result = InvoiceLogic.GetInvoiceDate(instant, day);
Speaker: Alexey Golub @Tyrrrz
private static IEnumerable<TestCaseData> GetTestCases()
{
yield return new TestCaseData(
new DateTimeOffset(2019, 12, 05, 00, 00, 00, TimeSpan.Zero),
20,
new DateTimeOffset(2019, 11, 20, 00, 00, 00, TimeSpan.Zero)
);
yield return new TestCaseData(
new DateTimeOffset(2019, 12, 25, 00, 00, 00, TimeSpan.Zero),
22,
new DateTimeOffset(2019, 12, 20, 00, 00, 00, TimeSpan.Zero)
);
}
[Test]
[TestCaseSource(nameof(GetTestCases))]
public void GetInvoiceDate_Test(DateTimeOffset instant, int day, DateTimeOffset expectedResult)
{
// Act
var actualResult = InvoiceLogic.GetInvoiceDate(instant, day);
// Assert
Assert.That(actualResult, Is.EqualTo(expectedResult));
}
Speaker: Alexey Golub @Tyrrrz
Pure-impure segregation
Speaker: Alexey Golub @Tyrrrz
public static class Program
{
public static void Main(string[] args)
{
var max = int.Parse(Console.ReadLine());
for (var i = 1; i <= max; i++)
{
if (i % 2 == 0)
Console.WriteLine(i);
}
}
}
Logic
Side-effects
Speaker: Alexey Golub @Tyrrrz
public static class Program
{
public static IEnumerable<int> EnumerateEvenNumbers(int max)
=> Enumerable.Range(1, max).Where(i => i % 2 == 0);
public static void Main(string[] args)
{
var max = int.Parse(Console.ReadLine());
foreach (var number in EnumerateEvenNumbers(max))
Console.WriteLine(number);
}
}
Logic
Side-effects
Speaker: Alexey Golub @Tyrrrz
public static class Program
{
public static TOut Pipe<TIn, TOut>(this TIn in, Func<TIn, TOut> transform)
=> transform(in);
public static IEnumerable<int> EnumerateEvenNumbers(int max)
=> Enumerable.Range(1, max).Where(i => i % 2 == 0);
public static void Main(string[] args)
{
Console.ReadLine()
.Pipe(int.Parse)
.Pipe(EnumerateEvenNumbers)
.ToList()
.ForEach(Console.WriteLine);
}
}
Speaker: Alexey Golub @Tyrrrz
private readonly ICounterpartyRepository _counterpartyRepository;
private readonly ILogger _logger;
/* ... */
public async Task<Counterparty> GetCounterpartyAsync(Transaction transaction)
{
var counterparties = await _counterpartyRepository.GetAll().ToArrayAsync();
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
_logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'");
return counterparty;
}
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
public static async Task<Counterparty> GetCounterpartyAsync(
ICounterpartyRepository counterpartyRepository, ILogger logger, Transaction transaction)
{
var counterparties = await counterpartyRepository.GetAll().ToArrayAsync();
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'");
return counterparty;
}
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
public delegate Task<IReadOnlyList<Counterparty>> AsyncCounterpartyResolver();
public delegate void LogHandler(string message);
public static async Task<Counterparty> GetCounterpartyAsync(
AsyncCounterpartyResolver getCounterpartiesAsync,
LogHandler log,
Transaction transaction)
{
var counterparties = await getCounterpartiesAsync();
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
log($"Transaction {transaction.Id} routed to '{counterparty}'");
return counterparty;
}
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
public static Counterparty GetCounterparty(
IReadOnlyList<Counterparty> counterparties, LogHandler log, Transaction transaction)
{
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
log($"Transaction {transaction.Id} routed to '{counterparty}'");
return counterparty;
}
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
public static Counterparty GetCounterparty(
IReadOnlyList<Counterparty> counterparties, Transaction transaction)
{
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
return counterparty;
}
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
public static Counterparty? TryGetCounterparty(
IReadOnlyList<Counterparty> counterparties, Transaction transaction)
{
foreach (var counterparty in counterparties)
{
if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type))
continue;
if (!counterparty.SupportedCurrencies.Contains(transaction.Currency))
continue;
return counterparty;
}
return null;
}
Speaker: Alexey Golub @Tyrrrz
public static IEnumerable<Counterparty> GetAvailableCounterparties(
IEnumerable<Counterparty> counterparties, Transaction transaction)
{
return counterparties
.Where(c => c.SupportedTransactionTypes.Contains(transaction.Type))
.Where(c => c.SupportedCurrencies.Contains(transaction.Currency))
}
Speaker: Alexey Golub @Tyrrrz
private readonly ICounterpartyRepository _counterpartyRepository;
private readonly ILogger _logger;
/* ... */
public async Task<Counterparty> GetCounterpartyAsync(Transaction transaction)
{
var counterparties = await _counterpartyRepository.GetAll().ToArrayAsync();
var counterparty = CounterpartyLogic
.GetAvailableCounterparties(counterparties, transaction)
.FirstOrDefault();
_logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'");
return counterparty ??
throw new CounterpartyNotFoundException("No counterparty found to execute this transaction.");
}
Speaker: Alexey Golub @Tyrrrz
[HttpGet]
public async Task<IActionResult> GetCounterparty(Transaction transaction)
{
var counterparties = await _dbContext.Counterparties.ToArrayAsync();
var counterparty = CounterpartyLogic
.GetAvailableCounterparties(counterparties, transactions)
.FirstOrDefault();
_logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'");
if (counterparty is null)
return NotFound("No counterparty found to execute this transaction.");
return Ok(counterparty);
}
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Controller
Speaker: Alexey Golub @Tyrrrz
DbContext
Controller
Pull
data
Filter
counterparties
Select
counterparty
Calculate
fee
Deduct
balance
Push
data
… …
Pure-impure segregation principle
• Impure functions can call pure functions
• Pure functions cannot call impure functions
• Impure functions should be pushed outwards
• Work towards maximum purity
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Pure layer
Impure layer
System boundary
Testing
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Unit tests
Functional tests
Integration
tests
Speaker: Alexey Golub @Tyrrrz
Functional tests
Unit tests
Integration
tests
Speaker: Alexey Golub @Tyrrrz
Functional tests
Logic tests
Integration
tests
Recipe for reliable tests
1. Cover functional requirements (Functional tests)
2. Cover business logic (Logic tests)
3. Cover system-wide integration (Integration tests)
Speaker: Alexey Golub @Tyrrrz
Functional tests shouldn’t be difficult
• TestServer (ASP.NET Core)
• WebApplicationFactory (ASP.NET Core)
• Browser (NancyFx)
• TestConsole (System.CommandLine)
• VirtualConsole (CliFx)
• Docker (*anything*)
Speaker: Alexey Golub @Tyrrrz
Summary
• Avoid introducing dependencies
• Avoid meaningless abstractions
• Avoid tests that rely on mocks
• Avoid cargo cult programming
• Prefer pure-impure segregation
• Prefer pure functions for business logic
• Prefer pipelines to hierarchies
• Prefer functional tests
Speaker: Alexey Golub @Tyrrrz
Consider checking out
• Functional architecture by Mark Seemann
• Async injection by Mark Seemann
• Test-induced damage by David Heinemeier Hansson
• TDD is dead, long live testing by David Heinemeier Hansson
• Functional principles for OOD by Jessica Kerr
• Railway-oriented programming by Scott Wlaschin
Speaker: Alexey Golub @Tyrrrz
Thank you!
Speaker: Alexey Golub @Tyrrrz

More Related Content

What's hot

Data collection in AWS at Schibsted
Data collection in AWS at SchibstedData collection in AWS at Schibsted
Data collection in AWS at SchibstedLars Marius Garshol
 
Distributed Real-Time Stream Processing: Why and How 2.0
Distributed Real-Time Stream Processing:  Why and How 2.0Distributed Real-Time Stream Processing:  Why and How 2.0
Distributed Real-Time Stream Processing: Why and How 2.0Petr Zapletal
 
Reactive Programming with Rx
 Reactive Programming with Rx Reactive Programming with Rx
Reactive Programming with RxC4Media
 
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et Kibana
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et KibanaJournée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et Kibana
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et KibanaPublicis Sapient Engineering
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibanadknx01
 
Cloud Dataflow - A Unified Model for Batch and Streaming Data Processing
Cloud Dataflow - A Unified Model for Batch and Streaming Data ProcessingCloud Dataflow - A Unified Model for Batch and Streaming Data Processing
Cloud Dataflow - A Unified Model for Batch and Streaming Data ProcessingDoiT International
 
Compliance as Code with terraform-compliance
Compliance as Code with terraform-complianceCompliance as Code with terraform-compliance
Compliance as Code with terraform-complianceEmre Erkunt
 
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun..."ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...Julia Cherniak
 
Docker Logging and analysing with Elastic Stack
Docker Logging and analysing with Elastic StackDocker Logging and analysing with Elastic Stack
Docker Logging and analysing with Elastic StackJakub Hajek
 
Cassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra ApplicationsCassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra ApplicationsChristopher Batey
 
OpenStack APIs: Present and Future (Beta Talk)
OpenStack APIs: Present and Future (Beta Talk)OpenStack APIs: Present and Future (Beta Talk)
OpenStack APIs: Present and Future (Beta Talk)Wade Minter
 
Elk with Openstack
Elk with OpenstackElk with Openstack
Elk with OpenstackArun prasath
 
Reactive Streams: Handling Data-Flow the Reactive Way
Reactive Streams: Handling Data-Flow the Reactive WayReactive Streams: Handling Data-Flow the Reactive Way
Reactive Streams: Handling Data-Flow the Reactive WayRoland Kuhn
 
Machine Learning in a Twitter ETL using ELK
Machine Learning in a Twitter ETL using ELK Machine Learning in a Twitter ETL using ELK
Machine Learning in a Twitter ETL using ELK hypto
 
Realtime Data Analytics
Realtime Data AnalyticsRealtime Data Analytics
Realtime Data AnalyticsBo Yang
 
Async – react, don't wait
Async – react, don't waitAsync – react, don't wait
Async – react, don't waitJohan Andrén
 

What's hot (20)

Data collection in AWS at Schibsted
Data collection in AWS at SchibstedData collection in AWS at Schibsted
Data collection in AWS at Schibsted
 
Distributed Real-Time Stream Processing: Why and How 2.0
Distributed Real-Time Stream Processing:  Why and How 2.0Distributed Real-Time Stream Processing:  Why and How 2.0
Distributed Real-Time Stream Processing: Why and How 2.0
 
Reactive Programming with Rx
 Reactive Programming with Rx Reactive Programming with Rx
Reactive Programming with Rx
 
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et Kibana
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et KibanaJournée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et Kibana
Journée DevOps : Des dashboards pour tous avec ElasticSearch, Logstash et Kibana
 
Dapper performance
Dapper performanceDapper performance
Dapper performance
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibana
 
Cloud Dataflow - A Unified Model for Batch and Streaming Data Processing
Cloud Dataflow - A Unified Model for Batch and Streaming Data ProcessingCloud Dataflow - A Unified Model for Batch and Streaming Data Processing
Cloud Dataflow - A Unified Model for Batch and Streaming Data Processing
 
Presto overview
Presto overviewPresto overview
Presto overview
 
Compliance as Code with terraform-compliance
Compliance as Code with terraform-complianceCompliance as Code with terraform-compliance
Compliance as Code with terraform-compliance
 
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun..."ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...
"ClojureScript journey: from little script, to CLI program, to AWS Lambda fun...
 
Docker Logging and analysing with Elastic Stack
Docker Logging and analysing with Elastic StackDocker Logging and analysing with Elastic Stack
Docker Logging and analysing with Elastic Stack
 
Cassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra ApplicationsCassandra Summit EU 2014 - Testing Cassandra Applications
Cassandra Summit EU 2014 - Testing Cassandra Applications
 
OpenStack APIs: Present and Future (Beta Talk)
OpenStack APIs: Present and Future (Beta Talk)OpenStack APIs: Present and Future (Beta Talk)
OpenStack APIs: Present and Future (Beta Talk)
 
Elk with Openstack
Elk with OpenstackElk with Openstack
Elk with Openstack
 
Elk stack
Elk stackElk stack
Elk stack
 
Reactive Streams: Handling Data-Flow the Reactive Way
Reactive Streams: Handling Data-Flow the Reactive WayReactive Streams: Handling Data-Flow the Reactive Way
Reactive Streams: Handling Data-Flow the Reactive Way
 
Machine Learning in a Twitter ETL using ELK
Machine Learning in a Twitter ETL using ELK Machine Learning in a Twitter ETL using ELK
Machine Learning in a Twitter ETL using ELK
 
Dapper
DapperDapper
Dapper
 
Realtime Data Analytics
Realtime Data AnalyticsRealtime Data Analytics
Realtime Data Analytics
 
Async – react, don't wait
Async – react, don't waitAsync – react, don't wait
Async – react, don't wait
 

Similar to Alexey Golub on Functional Architecture for Testable and Maintainable Applications

GitHub Actions in action
GitHub Actions in actionGitHub Actions in action
GitHub Actions in actionOleksii Holub
 
DevOps Fest 2020. Alexey Golub. GitHub Actions in action
DevOps Fest 2020. Alexey Golub. GitHub Actions in actionDevOps Fest 2020. Alexey Golub. GitHub Actions in action
DevOps Fest 2020. Alexey Golub. GitHub Actions in actionDevOps_Fest
 
Fallacies of unit testing
Fallacies of unit testingFallacies of unit testing
Fallacies of unit testingOleksii Holub
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomyDongmin Yu
 
5 x HTML5 worth using in APEX (5)
5 x HTML5 worth using in APEX (5)5 x HTML5 worth using in APEX (5)
5 x HTML5 worth using in APEX (5)Christian Rokitta
 
Akka with Scala
Akka with ScalaAkka with Scala
Akka with ScalaOto Brglez
 
Gojko Adzic Cucumber
Gojko Adzic CucumberGojko Adzic Cucumber
Gojko Adzic CucumberSkills Matter
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on AndroidSven Haiges
 
Mobile Developers Talks: Delve Mobile
Mobile Developers Talks: Delve MobileMobile Developers Talks: Delve Mobile
Mobile Developers Talks: Delve MobileKonstantin Loginov
 
Oleksii Holub "Expression trees in C#"
Oleksii Holub "Expression trees in C#" Oleksii Holub "Expression trees in C#"
Oleksii Holub "Expression trees in C#" Fwdays
 
Microservices and modularity with java
Microservices and modularity with javaMicroservices and modularity with java
Microservices and modularity with javaDPC Consulting Ltd
 
Event-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 EngineEvent-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 EngineRicardo Silva
 
Expression trees in C#
Expression trees in C#Expression trees in C#
Expression trees in C#Oleksii Holub
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on AndroidTomáš Kypta
 
"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition
"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition
"Enabling Googley microservices with gRPC" VoxxedDays Minsk editionAlex Borysov
 
NET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxNET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxpetabridge
 
REST meets Semantic Web
REST meets Semantic WebREST meets Semantic Web
REST meets Semantic WebSteve Speicher
 
What to expect from Java 9
What to expect from Java 9What to expect from Java 9
What to expect from Java 9Ivan Krylov
 

Similar to Alexey Golub on Functional Architecture for Testable and Maintainable Applications (20)

GitHub Actions in action
GitHub Actions in actionGitHub Actions in action
GitHub Actions in action
 
DevOps Fest 2020. Alexey Golub. GitHub Actions in action
DevOps Fest 2020. Alexey Golub. GitHub Actions in actionDevOps Fest 2020. Alexey Golub. GitHub Actions in action
DevOps Fest 2020. Alexey Golub. GitHub Actions in action
 
Fallacies of unit testing
Fallacies of unit testingFallacies of unit testing
Fallacies of unit testing
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
 
Annotation processing tool
Annotation processing toolAnnotation processing tool
Annotation processing tool
 
5 x HTML5 worth using in APEX (5)
5 x HTML5 worth using in APEX (5)5 x HTML5 worth using in APEX (5)
5 x HTML5 worth using in APEX (5)
 
Akka with Scala
Akka with ScalaAkka with Scala
Akka with Scala
 
Gojko Adzic Cucumber
Gojko Adzic CucumberGojko Adzic Cucumber
Gojko Adzic Cucumber
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Nodejs - A quick tour (v4)
Nodejs - A quick tour (v4)Nodejs - A quick tour (v4)
Nodejs - A quick tour (v4)
 
Mobile Developers Talks: Delve Mobile
Mobile Developers Talks: Delve MobileMobile Developers Talks: Delve Mobile
Mobile Developers Talks: Delve Mobile
 
Oleksii Holub "Expression trees in C#"
Oleksii Holub "Expression trees in C#" Oleksii Holub "Expression trees in C#"
Oleksii Holub "Expression trees in C#"
 
Microservices and modularity with java
Microservices and modularity with javaMicroservices and modularity with java
Microservices and modularity with java
 
Event-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 EngineEvent-driven IO server-side JavaScript environment based on V8 Engine
Event-driven IO server-side JavaScript environment based on V8 Engine
 
Expression trees in C#
Expression trees in C#Expression trees in C#
Expression trees in C#
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
 
"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition
"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition
"Enabling Googley microservices with gRPC" VoxxedDays Minsk edition
 
NET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxNET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptx
 
REST meets Semantic Web
REST meets Semantic WebREST meets Semantic Web
REST meets Semantic Web
 
What to expect from Java 9
What to expect from Java 9What to expect from Java 9
What to expect from Java 9
 

Recently uploaded

Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxBkGupta21
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 

Recently uploaded (20)

Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptx
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 

Alexey Golub on Functional Architecture for Testable and Maintainable Applications

  • 1. Speaker: Alexey Golub @Tyrrrz Dependency absolution Application as a pipeline
  • 2. Who am I? Speaker: Alexey Golub @Tyrrrz Open source developer • https://github.com/Tyrrrz • 6 active projects • 4000 total stars • 400k total downloads Senior software developer • Svitla Systems, Inc • C#, ASP.NET Core, Docker, AWS Interests • C#, F#, JavaScript • Functional programming • Language design, parsing, DSLs • Product design, photography
  • 3. Best code is no code at all Bad design decisions lead to writing more code in the long run Speaker: Alexey Golub @Tyrrrz
  • 5. Object-oriented approach Speaker: Alexey Golub @Tyrrrz public class Ledger { public Guid Id { get; } public decimal Balance { get; private set; } public Ledger(Guid id, decimal initialBalance) { /* ... */ } public void Debit(decimal amount) { /* ... */ } public void Credit(decimal amount) { /* ... */ } public static Ledger Create(decimal initialBalance = 0.0M) { /* ... */ } public static Ledger Retrieve(Guid id) { /* ... */ } }
  • 6. public void Debit(decimal amount) { if (Balance < amount) throw new InsufficientFundsException(); Balance -= amount; Database.Save(this); } Speaker: Alexey Golub @Tyrrrz Business logic Dependency
  • 7. Speaker: Alexey Golub @Tyrrrz Object Dependency
  • 8. Speaker: Alexey Golub @Tyrrrz Object Dependency Object Abstraction Dependency Mock
  • 9. public void Debit(decimal amount) { if (Balance < amount) throw new InsufficientFundsException(); Balance -= amount; ServiceLocator.GetService<IDatabase>().Save(this); } Speaker: Alexey Golub @Tyrrrz
  • 10. public void Debit(decimal amount) { if (Balance < amount) throw new InsufficientFundsException(); Balance -= amount; ServiceLocator.GetService<IDatabase>().Save(this); } private readonly IDatabase _database; /* ... */ public void Debit(decimal amount) { if (Balance < amount) throw new InsufficientFundsException(); Balance -= amount; _database.Save(this); } Speaker: Alexey Golub @Tyrrrz Abstract dependency
  • 11. Benefits of dependency injection • Inversion of control • Lifetime management • Decoupled implementations • Reduced module complexity • Isolated testing Speaker: Alexey Golub @Tyrrrz
  • 13. Speaker: Alexey Golub @Tyrrrz Domain models Data access Services System boundary
  • 14. Layered/onion architecture public class Ledger { public Guid Id { get; set; } public decimal Amount { get; set; } } public interface ILedgerRepository { Task AddAsync(Ledger ledger); Task UpdateAsync(Ledger ledger); Task<Ledger> RetrieveAsync(Guid id); } public interface ILedgerService { Task<Ledger> CreateAsync(decimal initialBalance = 0.0M); Task<Ledger> RetrieveAsync(Guid id); Task DebitAsync(Guid id, decimal amount); Task CreditAsync(Guid id, decimal amount); } Speaker: Alexey Golub @Tyrrrz
  • 15. Speaker: Alexey Golub @Tyrrrz Ledger LedgerRepository LedgerService LedgerController System boundary (entry point) Service layer Data access layer Domain model
  • 16. Speaker: Alexey Golub @Tyrrrz Ledger LedgerRepository LedgerService LedgerController TransactionFee Counterparty TransactionController TransactionService FeeService Statement StatementService CounterpartyService FeeRepository TransactionRepositoryCounterpartyRepository StatementRepository ScheduledJob NotificationService
  • 17. Layers of issues Speaker: Alexey Golub @Tyrrrz
  • 18. Hard to combine with OOD • Existing paradigms became anti-patterns • Encapsulation is gone • Inheritance is disfavored • Static classes & methods are “untestable” Speaker: Alexey Golub @Tyrrrz
  • 19. Causational indirectness • Hard to trace • Hard to reason about • Implicit dependencies & DI made us lazy Speaker: Alexey Golub @Tyrrrz
  • 20. Leaky async • Async stems from IO side-effects and leaks into business logic • Unnecessary state machines impact performance • Code becomes redundantly verbose Speaker: Alexey Golub @Tyrrrz
  • 21. public class LedgerService : ILedgerService { private readonly ILedgerRepository _repository; /* ... */ public async Task DebitAsync(Guid ledgerId, decimal amount) { var ledger = await _repository.GetByIdAsync(ledgerId); if (ledger is null) throw new EntityNotFoundException(); if (ledger.Balance < amount) throw new InsufficientFundsException(); ledger.Balance -= amount; await _repository.SaveChangesAsync(); } } Speaker: Alexey Golub @Tyrrrz Leaky async
  • 22. public interface ILedgerService { Task<Ledger> CreateAsync(decimal initialBalance = 0.0M); Task<Ledger> RetrieveAsync(Guid id); Task DebitAsync(Guid id, decimal amount); Task CreditAsync(Guid id, decimal amount); } Speaker: Alexey Golub @Tyrrrz ILedgerService reveals the fact that LedgerService depends on ILedgerRepository
  • 23. Obscured complexity • Module complexity is reduced • Total complexity is increased • Assumptions between communicating modules Speaker: Alexey Golub @Tyrrrz
  • 24. Mock-based testing • Implementation-aware • Very brittle • Massive time sink Speaker: Alexey Golub @Tyrrrz
  • 25. // Arrange var transactionRepositoryMock = new Mock<ITransactionRepository>(); transactionRepositoryMock.Setup(x => x.GetAll()) .Returns(testData.AsQueryable()); var counterpartyServiceMock = new Mock<ICounterpartyService>(); counterpartyServiceMock.Setup(x => x.GetCounterpartyAsync(It.IsAny<Transaction>())) .ReturnsAsync(testCounterparty); var transactionService = new TransactionService( transactionRepositoryMock.Object, counterpartyServiceMock.Object ); var transaction = new Transaction { // ... } // Act await transactionService.ExecuteTransactionAsync(transaction); // Assert // ... Speaker: Alexey Golub @Tyrrrz Implementation-aware
  • 26. Autotelic abstractions • Every object requires an explicit abstraction • Abstractions are needed for the sole purpose of mocking • Abstractions don’t try to encapsulate behavior • Abstractions are owned by implementations instead of consumers Speaker: Alexey Golub @Tyrrrz
  • 27. Abstraction is a great tool and a terrible goal Speaker: Alexey Golub @Tyrrrz
  • 28. Is OOP the wrong tool for the job? Speaker: Alexey Golub @Tyrrrz
  • 31. Side-effects Data transformation Speaker: Alexey Golub @Tyrrrz Validating, parsing, mapping, ordering, filtering, projecting… Reading request, writing to DB, enqueuing messages…
  • 35. Speaker: Alexey Golub @Tyrrrz F(x)Data in Data out Isolation SQL
  • 37. Speaker: Alexey Golub @Tyrrrz var fees = _invoiceService.CalculateFees(); Can I make any assumptions about _invoiceService?
  • 38. Speaker: Alexey Golub @Tyrrrz var fees = InvoiceLogic.CalculateFees(ledger, invoiceDate); Dependencies are clear which makes the intent clear
  • 40. private readonly CommonDbContext _dbContext; /* ... */ public IEnumerable<Transaction> GetCompletedTransactions(Guid ledgerId) { return _dbContext.Transactions .Where(t => t.LedgerId == ledgerId) .Where(t => t.IsCompleted); } Speaker: Alexey Golub @Tyrrrz Data container
  • 41. Speaker: Alexey Golub @Tyrrrz public static IEnumerable<Transaction> GetCompletedTransactions( IEnumerable<Transaction> allTransactions, Guid ledgerId) { return allTransactions .Where(t => t.LedgerId == ledgerId) .Where(t => t.IsCompleted); }
  • 43. public static DateTimeOffset GetInvoiceDate(int day) { var instant = DateTimeOffset.Now; var potentialInvoiceDate = instant.Day >= day ? instant.AddDays(day - instant.Day) : instant.AddMonths(-1).AddDays(day - instant.Day); if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Saturday) potentialInvoiceDate = potentialInvoiceDate.AddDays(-1); else if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Sunday) potentialInvoiceDate = potentialInvoiceDate.AddDays(-2); return potentialInvoiceDate; } Non-deterministic Speaker: Alexey Golub @Tyrrrz
  • 44. public static DateTimeOffset GetInvoiceDate(DateTimeOffset instant, int day) { var potentialInvoiceDate = instant.Day >= day ? instant.AddDays(day - instant.Day) : instant.AddMonths(-1).AddDays(day - instant.Day); if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Saturday) potentialInvoiceDate = potentialInvoiceDate.AddDays(-1); else if (potentialInvoiceDate.DayOfWeek == DayOfWeek.Sunday) potentialInvoiceDate = potentialInvoiceDate.AddDays(-2); return potentialInvoiceDate; } Speaker: Alexey Golub @Tyrrrz
  • 46. var result = InvoiceLogic.GetInvoiceDate(instant, day); Speaker: Alexey Golub @Tyrrrz
  • 47. private static IEnumerable<TestCaseData> GetTestCases() { yield return new TestCaseData( new DateTimeOffset(2019, 12, 05, 00, 00, 00, TimeSpan.Zero), 20, new DateTimeOffset(2019, 11, 20, 00, 00, 00, TimeSpan.Zero) ); yield return new TestCaseData( new DateTimeOffset(2019, 12, 25, 00, 00, 00, TimeSpan.Zero), 22, new DateTimeOffset(2019, 12, 20, 00, 00, 00, TimeSpan.Zero) ); } [Test] [TestCaseSource(nameof(GetTestCases))] public void GetInvoiceDate_Test(DateTimeOffset instant, int day, DateTimeOffset expectedResult) { // Act var actualResult = InvoiceLogic.GetInvoiceDate(instant, day); // Assert Assert.That(actualResult, Is.EqualTo(expectedResult)); } Speaker: Alexey Golub @Tyrrrz
  • 49. public static class Program { public static void Main(string[] args) { var max = int.Parse(Console.ReadLine()); for (var i = 1; i <= max; i++) { if (i % 2 == 0) Console.WriteLine(i); } } } Logic Side-effects Speaker: Alexey Golub @Tyrrrz
  • 50. public static class Program { public static IEnumerable<int> EnumerateEvenNumbers(int max) => Enumerable.Range(1, max).Where(i => i % 2 == 0); public static void Main(string[] args) { var max = int.Parse(Console.ReadLine()); foreach (var number in EnumerateEvenNumbers(max)) Console.WriteLine(number); } } Logic Side-effects Speaker: Alexey Golub @Tyrrrz
  • 51. public static class Program { public static TOut Pipe<TIn, TOut>(this TIn in, Func<TIn, TOut> transform) => transform(in); public static IEnumerable<int> EnumerateEvenNumbers(int max) => Enumerable.Range(1, max).Where(i => i % 2 == 0); public static void Main(string[] args) { Console.ReadLine() .Pipe(int.Parse) .Pipe(EnumerateEvenNumbers) .ToList() .ForEach(Console.WriteLine); } } Speaker: Alexey Golub @Tyrrrz
  • 52. private readonly ICounterpartyRepository _counterpartyRepository; private readonly ILogger _logger; /* ... */ public async Task<Counterparty> GetCounterpartyAsync(Transaction transaction) { var counterparties = await _counterpartyRepository.GetAll().ToArrayAsync(); foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; _logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'"); return counterparty; } throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 53. public static async Task<Counterparty> GetCounterpartyAsync( ICounterpartyRepository counterpartyRepository, ILogger logger, Transaction transaction) { var counterparties = await counterpartyRepository.GetAll().ToArrayAsync(); foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'"); return counterparty; } throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 54. public delegate Task<IReadOnlyList<Counterparty>> AsyncCounterpartyResolver(); public delegate void LogHandler(string message); public static async Task<Counterparty> GetCounterpartyAsync( AsyncCounterpartyResolver getCounterpartiesAsync, LogHandler log, Transaction transaction) { var counterparties = await getCounterpartiesAsync(); foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; log($"Transaction {transaction.Id} routed to '{counterparty}'"); return counterparty; } throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 55. public static Counterparty GetCounterparty( IReadOnlyList<Counterparty> counterparties, LogHandler log, Transaction transaction) { foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; log($"Transaction {transaction.Id} routed to '{counterparty}'"); return counterparty; } throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 56. public static Counterparty GetCounterparty( IReadOnlyList<Counterparty> counterparties, Transaction transaction) { foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; return counterparty; } throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 57. public static Counterparty? TryGetCounterparty( IReadOnlyList<Counterparty> counterparties, Transaction transaction) { foreach (var counterparty in counterparties) { if (!counterparty.SupportedTransactionTypes.Contains(transaction.Type)) continue; if (!counterparty.SupportedCurrencies.Contains(transaction.Currency)) continue; return counterparty; } return null; } Speaker: Alexey Golub @Tyrrrz
  • 58. public static IEnumerable<Counterparty> GetAvailableCounterparties( IEnumerable<Counterparty> counterparties, Transaction transaction) { return counterparties .Where(c => c.SupportedTransactionTypes.Contains(transaction.Type)) .Where(c => c.SupportedCurrencies.Contains(transaction.Currency)) } Speaker: Alexey Golub @Tyrrrz
  • 59. private readonly ICounterpartyRepository _counterpartyRepository; private readonly ILogger _logger; /* ... */ public async Task<Counterparty> GetCounterpartyAsync(Transaction transaction) { var counterparties = await _counterpartyRepository.GetAll().ToArrayAsync(); var counterparty = CounterpartyLogic .GetAvailableCounterparties(counterparties, transaction) .FirstOrDefault(); _logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'"); return counterparty ?? throw new CounterpartyNotFoundException("No counterparty found to execute this transaction."); } Speaker: Alexey Golub @Tyrrrz
  • 60. [HttpGet] public async Task<IActionResult> GetCounterparty(Transaction transaction) { var counterparties = await _dbContext.Counterparties.ToArrayAsync(); var counterparty = CounterpartyLogic .GetAvailableCounterparties(counterparties, transactions) .FirstOrDefault(); _logger.LogInformation($"Transaction {transaction.Id} routed to '{counterparty}'"); if (counterparty is null) return NotFound("No counterparty found to execute this transaction."); return Ok(counterparty); } Speaker: Alexey Golub @Tyrrrz
  • 61. Speaker: Alexey Golub @Tyrrrz Controller
  • 62. Speaker: Alexey Golub @Tyrrrz DbContext Controller Pull data Filter counterparties Select counterparty Calculate fee Deduct balance Push data … …
  • 63. Pure-impure segregation principle • Impure functions can call pure functions • Pure functions cannot call impure functions • Impure functions should be pushed outwards • Work towards maximum purity Speaker: Alexey Golub @Tyrrrz
  • 64. Speaker: Alexey Golub @Tyrrrz Pure layer Impure layer System boundary
  • 66. Speaker: Alexey Golub @Tyrrrz Unit tests Functional tests Integration tests
  • 67. Speaker: Alexey Golub @Tyrrrz Functional tests Unit tests Integration tests
  • 68. Speaker: Alexey Golub @Tyrrrz Functional tests Logic tests Integration tests
  • 69. Recipe for reliable tests 1. Cover functional requirements (Functional tests) 2. Cover business logic (Logic tests) 3. Cover system-wide integration (Integration tests) Speaker: Alexey Golub @Tyrrrz
  • 70. Functional tests shouldn’t be difficult • TestServer (ASP.NET Core) • WebApplicationFactory (ASP.NET Core) • Browser (NancyFx) • TestConsole (System.CommandLine) • VirtualConsole (CliFx) • Docker (*anything*) Speaker: Alexey Golub @Tyrrrz
  • 71. Summary • Avoid introducing dependencies • Avoid meaningless abstractions • Avoid tests that rely on mocks • Avoid cargo cult programming • Prefer pure-impure segregation • Prefer pure functions for business logic • Prefer pipelines to hierarchies • Prefer functional tests Speaker: Alexey Golub @Tyrrrz
  • 72. Consider checking out • Functional architecture by Mark Seemann • Async injection by Mark Seemann • Test-induced damage by David Heinemeier Hansson • TDD is dead, long live testing by David Heinemeier Hansson • Functional principles for OOD by Jessica Kerr • Railway-oriented programming by Scott Wlaschin Speaker: Alexey Golub @Tyrrrz
  • 73. Thank you! Speaker: Alexey Golub @Tyrrrz