NDC London 2025
I have attended the NDC London 2025 conference and it was a wonderful experience. If you have not attended an NDC conference before, based on my experience from 2025, I would wholeheartedly recommend that you do. NDC conferences are organised in several countries, so you do not need to wait for the next conference in London to attend.
The ingredients that made my conference experience so enjoyable this year are:
- A very useful 2 day practical workshop on messaging.
- Insightful talks with lots of demos for the next 3 days.
- An awesome community which is welcoming to newcomers (like me).
- Exciting companies to engage with at their booths.
- An ad-hoc .NET London Meetup event during one of the evenings.
Overview of topics covered
I have attended a workshop and several talks covering the following topics:
- Artificial intelligence.
- Messaging in distributed systems.
- Software architecture.
- C# and .NET.
- Data visualisation.
Given that several workshops and talks were running concurrently most of the time the topics listed above are only a small subset of the topics covered by the conference.
I will summarize below my main takeaways for each topic.
Artificial intelligence talks
The building blocks that I think can be integrated to build great AI applications are:
- Local large language models (LLMs).
- Retrieval Augmented Generation (RAG).
- Predict + function call loops.
- Voice instead of text input.
- AI powered tools.
Over the next few weeks I will create sample demos using the building blocks listed above, so periodically come back to my blog if you are interested.
AI powered tools
VS Code
Useful features available in VS Code include:
- Ability to use per-project custom Copilot instructions.
- Button to automatically generate commit message.
- Copilot in the terminal.
- Ability to use Claude 3.5 Sonnet for code generation.
- “Edit with Copilot” for large scale code generation.
- Copilot commands: /tests, /fix, /explain.
Useful references
- For experimenting with local LLMs: LM Studio.
- Application for running local LLMs: ollama.
- Repository of AI models: HuggingFace.
Messaging in distributed systems talks and workshop
- In order to build large-scale distributed systems that are maintainable and evolvable over time the systems typically need to be split up into multiple components.
- These components usually need to interact and ideally they would be as loosely coupled as possible.
- Decoupling allows teams to work independently and an organisation to scale.
- Messaging can be used to allow components to be as loosely coupled as possible and to interact in a reliable manner.
- Queuing theory (from mathematics) can be used to do capacity planning.
- As long as components need to interact they need to be coupled to a certain extent.
- Data coupling allows components to be loosely coupled.
- Why switch from sync (e.g. HTTP) to async interactions between components? Resilience.
- There are design patterns for how to add messaging code to applications.
- Message pump = forever loop that reads message off channel + translate to other representation + dispatch.
- Break large messages into pieces (e.g 64-256KB).
- To send large messages use claim checks (i.e. store content in distributed storage => token, send token to receivers, receiver uses token to retrieve content from distributed storage).
- Messaging (has intent, uses commands) vs eventing (provides facts, events, notifications).
- Popular message queuing solution: Rabbit MQ.
- Popular streaming solution: Kafka.
- Popular C# frameworks: Brighter, MassTransit, NServiceBus
- Scaling out:
- Message queues: Increase # of consumers.
- Streams: Partition # of streams.
- Postel’s law: Enforce schemas on producer, be flexible on the consumer side
- Forward transitive schema compatibility is typically used
- Patterns for reliably performing writes in one transactional DB and another data store:
- Outbox model;
- Outbox and inbox model;
- Tailing DB log and writing to outbox using a sweeper.
- State change capture.
- Standards:
- AsyncAPI: like OpenAPI for messaging.
- CloudEvents: for defining headers.
- OTEL: Observability.
- Messaging patterns (involving requestor vs provider):
- In-Only: Provider is a consumer.
- Out-Only: Provider is a producer.
- In-Out: Both provider and requestor are consumers and producers.
- Typically messaging is used for In-Only patterns and eventing is used for Out-Only patterns.
- Red Panda Connect can be used to convert DB/stream/queue into stream/queue.
- Miro can be used to design interactions between services.
Useful resources
- From messaging workshop:
- Slides and excercises: https://github.com/iancooper/practical-messaging
- Solutions for exercises implemented in C#: https://github.com/iancooper/Practical-Messaging-Sharp/tree/exercises
- Book on building microservices: https://samnewman.io/books/building_microservices_2nd_edition/
- Fallacies of distributed computing: https://cacmb4.acm.org/opinion/interviews/254622-l-peter-deutsch-on-the-fallacies-of-distributed-computing/fulltext
- Enterprise Integration Patterns: https://www.amazon.com/o/asin/0321200683/ref=nosim/enterpriseint-20
- Transitional architecture: https://martinfowler.com/articles/patterns-legacy-displacement/transitional-architecture.html
- Domain storytelling: https://domainstorytelling.org/
- Value stream mapping: https://www.lean.org/the-lean-post/articles/understanding-the-fundamentals-of-value-stream-mapping/
Software architecture talks
Software architects
- “Architects aren’t the smartest people in the room. They make everyone else smarter.”
- What makes architects valuable to a business:
- Connect the different levels in an organisation.
- Use metaphors to help people understand concepts using their own language.
- Look at problems across multiple dimensions.
- Break down the problem into subproblems.
- Increase solution space by increasing dimensionality.
- Decouple sub-{problems,solutions}.
- Help people think logically rather than just follow trends.
- Learn from the real world, which is a good source for metaphors.
- Create as many options for the business as possible in order to attempt to maximize the likelihood of choosing the best possible solution.
- Standardise as much as possible in order to be able to spend most time on expanding the solution space.
- The higher the uncertainty, the higher the value of the options offered to the business.
- Help others understand using models, which should be as simple as possible.
- Custom/hand-made diagrams/models are good for two-way communication.
- Read around problems in order to understand well the context of the problem to solve.
- “Sketch, don’t draw”.
- Raise the level of abstraction (to deepen the thinking).
- Become stronger with resistance.
- Without changing beliefs and/or assumptions, you cannot change behaviours.
The Architect’s Paradox
- Some things from real life cannot be reduced to 0s and 1s and logic. Therefore the software systems we build are approximations of real life. Depending on how the approximations are built, they may be very difficult to adapt to changes in real life, which means there is a risk that we build systems that cannot adapt as needed over time.
- The architect’s paradox is caused by the fact that we are trying to hold everything or most things still (in terms of architecture/diagrams), whereas the business/world keeps changing. Therefore it is only a matter of time until the architectures/models we build break.
- SWE is an early/immature discipline. As a result there is no structured predictable way to architect software.
- Embracing emergentism (philosophy), which attempts to handle complexity by adoptions philosophies of movement, could help.
- Criticality is the ability to withstand changes outside the spec. Architects should focus on this.
- For more details read written work of Barry M O’Reilly.
Migrating legacy web applications to a modern stack
- Migration approaches:
- Big bang (risky).
- Strangler fig (risk is contained).
- Focus on ASP .NET Framework legacy web applications.
- Migrate using strangler fig pattern, .NET Upgrade Assistant and a reverse proxy called YARP (which runs in the same app in which the modern ASP .NET Core/Blazor code lies).
- LigerSharp package to import CSS and JS from legacy project.
- SystemWebAdapters namespace = shim that enables new and old to share auth + session state.
Modular monoliths
- Creating boundaries between modules is one of the hardest parts
- Talk to the business.
- Event storming.
- Bounded contexts.
- Bounded contexts represent an architectural choice. Microservices represent a deployment choice. The two are not necessarily linked.
- Split code by business needs/requirements first, tech second.
- Use separate schemas for data stores corresponding to different modules.
- Register module endpoints inside modules rather than inside cross-module components (e.g. Controllers).
- Use mediator pattern to ensure coupling between modules remains low.
- Communicate between modules using services or events.
- Sync communication within modules.
- Async communication between modules.
- HangFire, NServiceBus, Brighter, MassTransit.
- Use ArchUnit (and NDependCop) to enforce interaction rules between modules.
Useful resources
- EventStorming book: https://www.eventstorming.com/book
- Mediator design pattern: https://refactoring.guru/design-patterns/mediator.
Event driven architectures and domain driven design
- Event driven architectures (EDAs) adoption seems to be increasing because EDAs are becoming more and more accessible as a result of the ecosystem built around them.
- Tools: EventCatalog
- Standards: Arazzo, OpenAPI, AsyncAPI, xRegistry, cloudevents
- Prevent accidental back-pressure from consumers to producers using events carried state transfer (ECST).
- Bridge the gap between domain experts and devs using domain driven design (DDD) techniques to reduce the likelihood of shipping misunderstandings.
- Use EventStorming
- Switch from an implementation-first mindset to behaviour-first
- Debugging issues when using EDAs is hard. Observability can help a lot (especially tracing).
- Observability will make it possible for you to understand what a system is doing in PROD.
- Custom instrumentation is key.
- When traces comprise too many spans, for better DX, split trace out and add causal links between (sub-)traces.
- Do not include sensitive/PII data in spans.
- Popular observability tools: Datadog, honeycomb
- Tool for mocking synchronous interactions: Wiremock
Useful resources
- Eric Evans’ DDD blue book: https://www.domainlanguage.com/ddd/blue-book/.
C# and .NET talks
C# 14 and beyond
- Features likely to come to C# 14:
- Dictionary expressions.
- Modifiers on simple lambda parameters.
- Unbound generic types in nameof.
- Null-conditional assignment.
- Partial events and constructors.
- Field access in auto-properties.
- First-class spans.
- User-defined compound assignment operator.
- Faster async code execution (runtime changes required).
- Extension all the things.
Intro to .NET Aspire
- .NET Aspire = tools + templates + packages to build PROD distributed applications.
- OTEL + service discovery out-of-the-box.
- Supports running any executable or container.
- Samples available in the Aspire GitHub repo.
- Mocking support will be added in the future to help with testing.
- Aspire can be used with Azure Dev Cli (azd) for deployment.
- Resource visualizer will be added to dashboard in the future.
Data visualisation
- Lie Factor = Size of effect shown in graphic / Size of effect in data.
- Minimize this.
- Data-Ink Ratio = Data Ink / Total Ink.
- Maximize this.
Useful resources
- Edward Tufte’s books: https://www.edwardtufte.com/book/all-5-paperback-books/
Summary
To sum it up in one phrase, NDC London 2025 was a great learning experience and I hope to be able to attend future editions. If you are unsure if you should do the same, just do it!