Devternity 2021

My synopsis for the conference


Before going to DevTernity 2021 I had no expectations of what might be in there. It was just another conference that was forwarded to me by my manager, and I was just bored enough to decide to sign up for it. I haven’t checked the backgrounds of the presenters and wasn’t even aware that Uncle Bob himself will give a talk there.

During the conference, I was amazed by the technical level of presenters and the potential usefulness of the reports.

While I myself do not fully agree with, say, turning Java into a Haskell-like language as per Bartlomej presentation (just throw an exception, dude, why would we want to reinvent monads if even the majority of Haskell developers can’t properly explain what they are?), I still found his presentation most useful. At least now I see why sealed classes or records were introduced in Java 17, maybe we’ll even migrate there one day, who knows?

Jakubs’ presentation wasn’t just yet another talk on “monoliths bad/good, microservices good/bad”, instead he did an interesting comparison between those two approaches, and introduced an often overlooked Event Driven model. This is something to look at, and this is something that reminded me, that I still haven’t finished reading that Elixir textbook.

Last but not least was Allen Holub, with his much-needed reminder on what Agile was supposed to be, instead of whatever most of us are doing in large corporations. This talk actually reminded me of what I was trying to do, while signing as a Scrum Master for our team, and what I probably couldn’t deliver, because I was too stuck behind the “I need a certificate first” mentality.

I will post my synopsis from the presentation below. These are the notes I wrote down while listening to the conference. I do not own anything from there. I may have misheard or misunderstood something, and feel free to hit me up on Twitter or Linkedin if you have any comments, suggestions, or copyright holder requests.

Good Enough Architecture

By Stefan Tilkov @stilkov

Slides available at:

Different applications require different architecture, eg. security vs availability, scalability, logic, and so on. Do not assume that copying blindly one architecture from a different application will work on another application.

There is no “good” or “bad” architecture - always look at the requirements.


#1 - Non-extensive extensibility

E-Commerce provider with a global customer base, catalog, CMS, Shop, Fulfillment, Multi-tenant, and Highly customizable.

(Problems that large customers need extensive customization, while for smaller customers just anything would do)

#2 - Perilously Fine Granuality

Large-scale B2B food retailer, company-wide shop, a bunch of microservices for all the cases.

Teams worked on several microservices, with some overlaps.

Checkout & Support & Fulfillment & Billing -> all point to order service, order service has to support all 4 domains, any changes cascade to other teams.

=> Why would you want to cut your system into such small microservices?

Everybody wants to be Netflix, but nobody is. Netflix architecture works for Netflix, not for you!

Refactoring within the team is much easier than between teams.


Large-scale insurance system Model-driven development

100 devs 2 releases/year

All fields are codes, there is a huge excel sheet for deciphering codes for all the models within the organization.

=> Centralized responsibility hurts and creates bottlenecks

=> Faced with too much rigidity, developers will find a way around the rules

Just because you’re used to it, doesn’t mean it’s acceptable


Unified communication platform Straight-forward business logic Small teams of devs

2 groups of people: “Java is uncool” vs “JavaScript is something you can’t use in serious programming”

Solution: separate them as frontenders vs backenders? But the problem is that people didn’t want to cooperate! You’ll have to collaborate, you can’t just have two teams of sworn enemies arguing with each other.

When faced with unresponsible backend teams, frontender became fullstack devs, but they couldn’t support the required complexity


E-commerce shop 100-120 devs ~ 10 self-contained teams

More freedom for devs to develop and maintain their own modules. Module system got promoted and became a bunch of different systems.

=> Extremely loose coupling requires few rules, but they need to be enforced strictly.

#6 Free-style architecture

Skipped by presenter

#7 Cancerous growth

Financials services provider with independent brokers as clients ~ 30 devs 20 years of company history

Oracle Forms App & Oracle DB with lots of PL/SQL - historically. Then added Java / JSP. Then - .Net web. Then purchased another company with similar, but different solutions. Merged database, kept adding databases, then decided to add experimental C++ encryption lib with no decryption other than in that only lib

=> As a result, it ended up a huge mess. “Successful systems often end up with the worst architecture”

=> Unmanaged evolution will lead to complete chaos. If you won’t make choices - they will be made for you.

=> Don’t be afraid of some light architectural governance.

#8 Imrove with Less Intelligence

Bank with multiple CotS systems Highly proprietary integration solution phased out by the vendor A project launched to replace the commercial product with open source solution

Replace magical integration broker (routing, conversion, transformation, transcoding, transport, error handling, business logic) - with something far less complex. Pub/Sub routing, Transport, Error handling. Adapter for simple docker containers - got testability and understanding of how things are done.

=> Prefer smart endpoints and dumb pipes over overly complex, powerful middleware => Be brave enough to pick specific solutions and avoid over-generalization


  1. Don’t be afraid of architecture
  2. Choose the simplest things that will work
  3. Create evolvable structures
  4. Manage your systems’ architectural evolution
  5. Don’t build roadblocks - create value and get out of the way

What I wish I knew when started designing systems

By Jakub Nabradalik

Slides at:

His career started when he came out fresh from university and was expected to design a huge enterprise system.

Bad education? Lots of young people come to his workshops, try to design systems, and fail the same way he did.

Explicitly optimize on defined attribute: 1-3 max. Optimize on everything = optimize on nothing.

Performance? Scalability? Most apps require that you do not fuck it up and that’s it. “It’s the stupid shit we do that slows them down.”

If we can run Doom on a calculator powered by literal potatoes - why can’t we design normal systems?

If you don’t know which, choose:

Maintainability - how much time will people spend on keeling it alive Extensibility - how easy will it be to add new features …and if you are a startup Flexibility - how much can we reuse when we do a pivot


Systems most of you build these days could be split into 3 architecture flavors:

Modular monoliths single app with modules

Distributed synchronous systems Usually microservices

Event-driven distributed system

Modular monolith problems

Big risk deployments - Don’t do it if you don’t like stress or your business can die due to a prod problem

Broken windows effect: To have any quality, requires a lot of self-discipline (show me a group of 100 self-disciplined devs)

Too easy to break architecture: Requires a lot of effort on communicating architecture, or it goes down fast

Limited technology evolution: Retention problem - good people will leave when bored

People do not know what a module is

Definition of a module

“My definition of a module”: a module has a single responsibility - for a process (verb), not a data.

A vertical slice of all layers from DB access to even UI

Clearly identifiable API and collaborators

Has its own database (or at least table/collections). Other modules cannot read/write data to that DB, they have to talk with that API

Encapsulation: everything apart from the API is hidden and inaccessible

In a single Bounded Context (see DDD), but a bounded context can have several modules

A module is often a candidate to microservices

How small should a module be

Start with: [Cost calculator] <- Get cost for day - [Incoice creator] - invoice issued -> [Client]

  • add PDF generator + add Email Sender. Each of those should be modules too? => If two steps of a process require different knowledge, and can be written by two developers working with a contract - split it into two modules.

Small modules are easier to replace, refactor, debug. Use Unix philosophy!

Distributed synchronous system problems

Independent deployments or go home

You need to model with eventual consistency and take the speed of light into account.

Requires DevOps - each team is responsible for their work on the production.

Requires teams for tooling - and a lot of tooling

Communication inefficiency (HTTP overhead, latency), means you have to build read models

Communication complexity - can fail in many places: needs retries, fallbacks, circuit breaker. This means you need to learn resiliency

But if you don’t learn observability first, you won’t even know you have a problem.

If not reactive, sub-optimal CPU usage

End-to-end tests are not possible anymore

Event-Driven (async) distributed system problems

Someone has to do a Ph.D. on the message bus. “Just use Kafka” won’t help! Closed source, providers lie a lot, etc.

Don’t do it if you cannot have those people.

People create chaos. They do not understand public/team level / private events.

Event (Driven) != Event (Sourcing) != Event (Storming)

Event-driven event - communication pattern

Event Sourcing event - persistence pattern

Event storming event - business event

=> May sometimes look identical but doesn’t have to

How modules/microservices communicate

  Module A                               
  -> Query (question)
  -> Command (intention: do something)
  -> Event (information)
                                      ->  Module B

Query with microservices: N+1 problem Monolith - two very inefficient queries, microservices - ideally 0 external calls

Read model based on events: Services notify you of parameters, you keep the ones that are relevant to you in your database

Command: when process changes, may require changes in two modules. Ok when a single team owns them, problematic with a distributed team

Event: “Cost was calculated” -> by nature asyncronous, bla-bla-bla


A baseline is a line that is a base for the measurement of construction.

When you find an error on the producer side or do not have infinite event retention, you can send all the events again, to rebuild the read models in other modules/microservices.

When the problem is on the consumer side and you have infinite event retention (Kafka etc.) you may just move the offset to the beginning of the queue, and reprocess all events

What if your consumers are idempotent? Clear all DBs?

Data ownership vs Single point of truth

Single point of truth: only I can say “Car was rented”

Nobody owns events. Events are what has already happened (safe to duplicate)

Ensure invariants.

Invariant: “we can only rent a car that is not already rented”

Implementation: “optimistic lock on DB inside Car Rental”

Orchestration vs Choreography

Orchestration: a module that tells everyone else what to do - controls the process. That module team needs to understand all processes. This is quickly not true anymore, ok in a monolith built by a single team. Don’t do it in distributed systems, or between teams. Avoid synchronizations.

Choreography: Every module reacts to facts (events) and knows what it should do - it’s part of the internal logic of a module. Allows for autonomy. There is no view on the big process, you need to create a dashboard. Don’t be tempted to put compensation logic in the dashboard!

Different quality requirements.

Practical example:

Patient registration system

Patients registering for vaccination: low SLA, dynamic RPS (chaos!).

Nurses getting patient data: fixed RPS, high SLA.

Nurses’ data can’t crash if there’s an overload of registering patients!

=> Solution: Split functions with different quality requirements into separate apps

Availability recommendation

Avoid designing an app that needs high availability and will have a lot of changes

High availability is much easier when devs do not introduce new bugs

Even old shitty monoliths can become stable (if you don’t touch them)

Do not be afraid to split a module in two: high SLA / low change frequency process & low SLA / high change frequency process

Don’t be afraid of eventual consistency: to scale infinitely we must avoid coordination. life beyond distributed transactions: an apostates’ opinion, we need at-least-once delivery guarantees. Eventual consistency can be achieved even without a message bus, but even if you have a message bus, you have more options.

How to make your architecture scream with functional domain modeling

By Bartlomej Slokta @bartekslota

( This talk - is not a comprehensive introduction to FP )

Common way - fat service, large domain POJOs => results in the big ball of … something


  1. Overusing primitives. Too many business objects, but the logic is leaking from them to services. That way code doesn’t tell us much about how the system works.

Functional programming

Function - a mathematic concept that describes the relation between domain and rules. FP - programming with functions that always return values and have no side-effect. (pure functions)

Pure functions - do not change anything, do not rely on anything that can be changed.

Can we create a viable application without a state? No, we can’t.

Function: “String, String, Integer, Customer” -> Lending. This tells us nothing!

Function: VehicleId, StationId, ReservationDuration, Customer -> Lending. Now, that’s better!

record VehicleId(String value) {
  private static final String PATTERN = "[0-9A-Z]{15}";

  VehicleId {
    if(value == null || !value,matches(pattern)) {
      throw IllegalArgumentExceltion();

VehicleId, StationId, ReservationDuration, Customer => MakeReservationCommand.

FP: Algebraic types - types composed of different types. In Java it’s painful, but still.

Testability - UUID.randomUUID(), - will change, may affect our tests.

Supplier, Supplier - extract as dependencies.

MakeReservationCommand, IdSupplier, CliockSupplier -> Lending

Function Currying: Function<Supplier>, Function<Supplier, Function<MakeReservationCommand, Lending»> {}

Partial application: makeReservationWorkflow.apply(UUID::randomUUID).apply(Clock::systemUTC).apply(command)

Handling results

MakeReservationCommand -> Result<MaleReservationFailure, Lending>

Exceptions affect readability, think of wrapping exceptions into the result type.

Chaining Results: in FP we would create an adapter that could take two parameters and return two parameters.

default <S> Result<L, S> bind(Function<R, S> bindedFunction) {
  if(success()) return Result.success(bindedFunction.apply(getSuccess));
  return Result.failure(...);

Java result class described (capable of mapping both result or failure) - FP monad.


DB state changes - is ok, Object value is immutable.

Domain and codomain

sealed interface Lending permits PendingLending, FinishedLending

Function PendingLending -> FinishedLending

PendingLending - only information about ongoing lending. FinishedLending - information about finalized lending. Way better!

Workflow: (see diagram)

??? PendingReservation -> FinishedLending ???

FinishLendingCommand, PendingLending -> LendingFinishedEvent LendingFinshedEvent, PendingLending -> FinishedLending LendingFinishedEvent, PendingReservation -> UsedReservation

Meta model of the logic

Stable logic - stable process, small susceptibility to change Logic closure - many variants, many changes, more permissions, less descriptive

How to make your architecture scream?

  • Model your code thinking of functions and workflows instead of state
  • Have referential transparency in mind
  • Model your class hierarchies so that they correspond to the true domain and codomain of your business function
  • Avoid side effects like exceptions and mutability
  • Keep your domain pure - move IO to the outside layer of your onion
  • Use monadic types to improve readability and robustness
  • Use events to communicate between different class hierarchies (aggregates)

Domain Driven Refactoring

By Jimmy Bogard @jbogard

What is a domain model?

TDD: Red, Green, Refactor. Real codebase: ???, ???, Refactor!


  • Refactoring by Martin Fowler;
  • Working effectively with legacy code by Michael C. Feathers;
  • Refactoring to Patterns by Joshua Kerievsky

First things first, make sure that programmers use the same language as customers and domain experts.

Database diagram: shows a bunch of data.

Where’s the behavior?

Presenter uses C# for presentation, not that it matters much

Model: Entity, ExpirationType, Member, Offer, Offer type.

Services: AssignOfferHandler, ExploreOfferHandler

(I decided to skip this presentation, as C# is not my language, and previous presentations were already too interesting)

The Craftsmans’ Oath

By Robert Martin himself O_O

“On computable numbers, with an application to the Entscheidungsproblem” - by Alan Turing - this is where “modern” CS started.

“If you have never written in binary - this is something you should try one day”

1945: Turing writes code in binary, uses “add” and logical “not”, invents subroutine, invents and codes stack, invents floating point.

~ 1950s: core memory price: a dollar per bit

1953 Fortran - punching code on cards

1958 List - functional programming is there!

1954-1960: IBM rented 140 model 70x computers

“Cobol - good idea, bad implementation”

1965: 10000s 1401s. Rented for $2.5k / mo

Aptitude tests to learn if you can become a programmer.

1966 - Simula-67, 1968 - C

1970s Agile: the process used by disciplined professionals observed in the wild. Hordes of young men jumped into programming, a waterfall wouldn’t work anymore, had to switch development model

1980s Number of programmers in the world goes through the roof, developers follow

2001: Snowbird Agile Manifesto Forestalling a disaster

2000-2010-2020 - web, smartphones, IoT, wtf is happening?

“How many people were killed, because ‘if’ statements were wrong?” “You and I are killing people! We weren’t into this business to kill people”

Civilization depends upon us. In ways, it doesn’t yet understand. In ways we don’t yet understand.

“You are I - we rule the world”

“Some dumb software developer will do something wrong and tens of thousands will die - and then politicians will ask you - how could you let this happen”

If you won’t be responsible with your code - it’s only a matter of time until software development will get regulated by politicians.

Only discipline and ethics could prevent this.

The future of Programming as per Robert Martin:

  • Discipline
  • Standards
  • Ethics

The Craftsman’s Oath:

  1. I will not produce harmful code.
  2. The code that I produce will always be my best work.
  3. I will produce, with each release, a quick, sure, and repeatable proof that every element of the code works as it should.
  4. I will make frequent, small, releases so that I do not impede the progress of others.
  5. I will fearlessly and relentlessly improve our work at every opportunity.
  6. I will do all that I can to keep the productivity of myself, and others, as high as possible.
  7. I will continuously ensure that others can cover for me and that I can cover for them.
  8. I will produce estimates that are honest both in magnitude and precision.
  9. I will respect my fellow programmers for their ethics, standards, disciplines, and skill.
  10. I will never stop learning and improving my craft.

War is Peace, Freedom is Slavery, Ignorance is Strength, Scrum is Agile

By Allen Holub

A very interesting talk, reminding me that Agile - is not “just do Scrum”, or “just do what you’re doing, but with standups”. It’s not even a list of principles from Agile Manifesto - instead, look at the word:

We are uncovering better ways of developing software by doing it and helping others do it.

Agile process - is about learning and improving, instead of blindly obeying rules and.

“Building twice the garbage in half the time benefits nobody. Sooner, not faster”

A rigid framework is not agile by definition.


This post should (but not necessarily would) be updated in 5 ~ 7 days when the organizers will upload their slides. I missed a few talks that seemed interesting since I can not listen to 3 talks at the same time. Plus my Zoom was lagging, and I couldn’t connect.

Software Architecture for Developers - Simon Brown

Fundamentals of Software Architecture: An Engineering Approach - Mark Richards, Neal Ford

Designing Event-Driven Systems - Ben Stopford

Reactive Design Patterns - Roland Kuhn Dr.

Team Topologies - Matthew Skelton, Manuel Pais

Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans

Domain Modeling Made Functional by Scott Wlaschin

Functional Programming in Java