DiscoverNO SILVER BULLET
NO SILVER BULLET

NO SILVER BULLET

Author: Three Dots Labs

Subscribed: 23Played: 53
Share

Description

Based on nearly 20 years of working together on various projects, we discuss when it makes sense to move fast rather than aim for perfect code, and how to avoid technical debt that can kill your project.

We focus on making mindful engineering decisions instead of blindly following rules like “always do X” or “never do Y”. Different situations need different approaches to code quality.
13 Episodes
Reverse
Full episode notes: ⁠https://threedots.tech/episode/ddd-toolbox-not-religion/Quick takeawaysDomain complexity matters more than technical complexity - Most projects fail not because of technical challenges, but because they don’t handle the business domain well.DDD is a toolbox, not a religion - You don’t need to use every pattern from Domain-Driven Design. Pick what solves your actual problems.Start with the domain model - Understanding how the business works is more important than designing the perfect schema.Avoid solving imaginary problems - Spending months on frameworks or platforms before building actual features often leads to wasted effort.Strategic patterns are essential for everyone - Even if you don’t use tactical DDD patterns, thinking about core domains and module boundaries matters in every project.In this episode, we discuss why software projects become legacy code that nobody wants to touch.We talk about how Domain-Driven Design can help, but also why it’s often misunderstood or overused.Instead of treating DDD as an all-or-nothing approach, we suggest to use the ideas pragmatically - picking the patterns that solve real problems in your project.We share stories to show how focusing on domain complexity rather than technical complexity leads to better software.
In this episode, we talk about why software projects in regular jobs are delivered much slower compared to side projects, and what you can do about it.We share our journey from building hobby projects as teenagers to working in professional environments, and the differences we encountered.Developers are often kept isolated from product decisions and treated as “coding monkeys in golden cages” - just receiving tasks without understanding the why behind them.We discuss techniques like Event Storming that can help break down these barriers, and improve collaboration between developers and product managers.
Season 2 Trailer

Season 2 Trailer

2025-11-2500:45

After the summer break, we're back with a new season of No Silver Bullet. New episodes every second Wednesday.
Episode notes: https://threedots.tech/episode/ama-1/Quick takeawaysGo in the AI era: Go is excellent for AI applications, as the built-in concurrency makes orchestrating parallel API calls much easier than languages like PythonArchitecture philosophy: Clean Architecture isn’t always necessary. Start simple and add layers only when you feel the pain of complexity, not because someone said you shouldCareer transitions: Switching roles within tech is easier internally. Moving from sysadmin to developer works better within the same company where people already trust youGo design patterns: Small interfaces near usage is the Go way. Duck typing allows you to define interfaces where they’re used rather than in separate layersDistributed systems: Async communication often solves sync timeout issues. When dealing with chains of service calls, consider using messages instead of increasing timeoutsIn this special 10th episode, we answer community questions in our first AMA format before taking a summer break.We discuss Go’s role in AI development, Clean Architecture implementation, career transitions in tech, and distributed system timeouts.After the break, we’ll switch to pre-recorded episodes with improved production quality. We still plan running some live episodes like this one, so stay tuned!
Full episode notes: https://threedots.tech/episode/prs-that-get-merged-the-same-day/Quick takeawaysPrioritize reviews over new work - treat PRs as work that’s almost done and needs to be pushed to production quicklyBig PRs create a dead loop - when reviews take ages, developers make even bigger PRs to avoid multiple long waitsKnowledge sharing is the hidden benefit - code reviews aren’t just gatekeeping, they spread understanding of how things work across the teamOne-day cycle time is possible - start work in the morning and merge by end of day with proper team culture and practicesSplit work vertically and horizontally - break features into small slices and layers that multiple people can implement in parallelIn this episode, we discuss how to make code reviews fast and effective by keeping pull requests small.We explore why big PRs are problematic, what causes them, and practical strategies to create PRs that can be merged within a day.Instead of waiting days for reviews with 200 comments, we focus on techniques that help teams achieve smooth, fast review cycles where work flows quickly from code to production.Introduction
Full episode notes and transcript: https://threedots.tech/episode/event-driven-architecture/Quick takeawaysEvent-driven architecture (EDA) is powerful but tricky – it’s great for scaling and decoupling, but has many hidden traps.Observability is essential – debugging async systems without tracing, logs, and correlation IDs is almost impossible.Use the outbox pattern – it’s the safest way to publish events without losing data.Design events carefully – large, generic events can lead to tight coupling and painful refactors.Avoid over-engineering – sometimes synchronous systems or simple monoliths are just better.Start with sync if unsure – it’s easier to migrate from a well-structured synchronous system to async later than the other way around.
Full episode notes: https://threedots.tech/episode/sync-vs-async/Quick takeawaysStart with synchronous architecture by default - it’s simpler to understand, debug, and maintain for most use casesAsync architecture improves scalability and resilience - message queues and events help handle traffic spikes and failuresDesign matters more than the technology choice - tight coupling creates the same problems in both sync and async approachesConsider team experience - async architecture require more experienced teams and better tooling to handle new challengesAdjust as your system grows - external APIs, heavy operations, or the need to handle failures gracefully are good use casesHybrid approach - use both sync and async where they fit best, rather than forcing one over the otherIn this episode, we discuss when to choose synchronous versus asynchronous architecture for backend systems.We talk about the trade-offs between simple, predictable sync communication and the complexity but resilience of async approaches using message queues and event-driven architecture.Instead of picking one approach over another, we focus on understanding when each makes sense and how to avoid common pitfalls like distributed monoliths and over-engineering.Go Event-Driven trainingWatermill - our open-source Go library for working with message streamsEvent Storming - a design technique with a great unfinished ebook by Alberto BrandoliniCQRS (Command Query Responsibility Segregation) - a pattern that works well with both sync and async approachesOur CQRS article and Server-Sent Events postMessage brokers mentioned: RabbitMQ, Kafka, NATS, Google Cloud Pub/SubEvent schemas: Protobuf, Avro, CloudEventsEvent Sourcing - a pattern mentioned in context of recreating state from eventsClean Architecture/Hexagonal Architecture - architectural patterns mentioned for making sync/async migration easierIntroductionShow Notes
Episode notes: https://threedots.tech/episode/history-of-watermill/Quick takeawaysSolve real problems first - successful open source projects start by addressing actual needs, not by looking for problems to fit a solutionKeep breaking changes minimal - Watermill stayed on v1 for 6 years with no breaking changes in the core library, building trust with usersExamples and documentation are crucial - provide real-world examples with automated tests, not just simple “hello world” demosPromotion matters - creating a great library isn’t enough; you need to actively share it through conferences, blog posts, and communitiesBe patient with growth - Watermill took 7 years to reach 8,000 stars; overnight success in open source is rareIn this episode, we share the story of how Watermill, our event-driven library for Go, grew from a side project to a popular open source library with over 8,000 GitHub stars and 100+ contributors.We discuss the key decisions and strategies that helped make Watermill successful, from focusing on solving real problems to maintaining backward compatibility and building a community around the project.Watermill documentation — our open-source event-driven architecture library for GoWatermill repositoryGo Event-Driven training that complements WatermillReal World Examples
Unpopular opinions about Go

Unpopular opinions about Go

2025-05-0101:33:471

Full episode notes: https://threedots.tech/episode/unpopular-opinions-about-go/Quick takeawaysSimplicity isn’t enough for complex applications - while Go’s syntax is simple, complex applications still need proper design patterns; primitive code easily becomes spaghetti code in large projects.Reading the standard library isn’t the best way to learn Go - it’s optimized for different goals than typical applications and might be confusing for beginners.Router libraries are better than the standard HTTP package - libraries like Chi or Echo come with a nice high-level API.Struct-based configuration is better than the “optional pattern” - structs are easier to document, discover, and maintain than the popular With-options approach.There’s no one best project structure - starting small and evolving your structure as needed is better than following a dogmatic approach like the unofficial “Go project layout.”Writing stubs by hand is better than using mocking libraries - manually written stubs are easier to debug and encourage better interfaces than reflection-based mocking libraries.Code generation is better than reflect - for ORMs or dependency injection, it gives you compile-time checks and better performance.Generics are mostly useful for libraries, not application code - while everyone waited for them, they’re rarely needed in typical service-level code.Channels and goroutines can be overused - they add complexity and should only be used when concurrency is actually needed, not as a default approach.Go’s error handling is fine for most projects - explicit checks make code easier to read, though built-in stack traces would be helpful.Memory optimizations are often premature - micro-optimizations waste time for typical API services where network latency is the bottleneck.In this episode of No Silver Bullet, we share some of our unpopular takes on the Go programming language. After working with Go for eight years on all kinds of projects, we’ve seen many discussions about what idiomatic Go means. We talk about what worked for us, but we keep in mind that different projects have different needs. We question some common Go beliefs and share tips we’ve picked up along the way.Links:Wild Workouts - Our example Go project that shows a more complex application structureWatermill - Our Event-driven application library for GoHTTP Routers we recommend: Chi and EchoGo in One Evening - Our hands-on Training for learning Go quicklyClean Architecture episode - Previous episode that goes deeper into project organizationGo Developer Survey Results - Shows that ~75% of Go developers build API/RPC servicesGoogle’s Go Style Guide - Many useful ideas, but be careful about being too dogmatic about it
In this episode, we discuss how to learn effectively as a software engineer. Why some people seem to learn faster than others? What are some practical ways to speed up your learning? Instead of promising magical shortcuts to becoming a principal engineer in months, we focus on a more balanced approach that helps you build skills by mixing theory with practice.Quick takeawaysFocus on applying what you learn - reading books or watching videos isn’t enough without practiceBuild real projects that challenge you - trivial examples don’t expose you to the hidden complexities you’ll face in actual workExpect and embrace frustration - feeling stuck often means you’re learning something valuableLearn timeless concepts over framework-specific details - aim for universal software skills like modularizationMix theory with practice in small chunks - read a bit and code a bit, rather than consuming large amounts of content at onceNotesWe mentioned our learning platformDomain-Driven Design was referenced throughout - check “Implementing Domain-Driven Design” by Vaughn VernonWatermill - our open-source library mentioned as an example of a project that taught us while helping others: github.com/ThreeDotsLabs/watermillEvent-Driven Architecture traces back to 1950s, but was formalized about 20 years agoThe Repository pattern blog postFull episode notes and transcript: http://threedots.tech/episode/learning-software-skills-fast/
Full episode notes and transcript: https://threedots.tech/episode/is-clean-architecture-overengineering/In this episode of the No Silver Bullet podcast, we discuss Clean Architecture and whether it's overengineering or a best practice for organizing code.We talk about why the pattern is often controversial, when it makes sense to use it, and how to implement it effectively.We share our experiences using Clean Architecture across different projects and teams, including the common concerns developers have when first seeing it.Quick takeaways:Clean Architecture is most beneficial for complex projects with larger teams - for small teams or simple projects, it can become overengineering.Separation of concerns is the core benefit - keeping domain logic separate from implementation details makes code more maintainable.It's easy to go too far - using too many interfaces or too many layers without a clear reason creates unnecessary complexity.Start simple and evolve your architecture - don't force Clean Architecture from the start if your project doesn't need it yet.Understanding the "why" behind the pattern is crucial - blindly following it without understanding leads to poor implementations.Notes:Related patterns:The Dependency Inversion Principle: one of SOLID principles.Blog post: Introducing Clean Architecture: https://threedots.tech/post/introducing-clean-architecture/Blog post: Microservices test architecture: https://threedots.tech/post/microservices-test-architecture/Blog post: Repository Pattern in Go: https://threedots.tech/post/repository-pattern-in-go/Blog post: Combining DDD, CQRS, and Clean Architecture in Go: https://threedots.tech/post/ddd-cqrs-clean-architecture-combined/ (Mentioned as available)Example Go Project: Wild Workouts: https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-examplego-cleanarch Linter: https://github.com/roblaszczak/go-cleanarch
Quick takeawaysFrameworks promise productivity but often lead to issues as projects get larger and more complex.The Go community prefers small, focused libraries over frameworks due to Go's design philosophy influenced by Unix principles.Watch out for risks using frameworks like vendor lock-in, deprecation, and costly migrations that can take months.Explicit code is more maintainable than magic framework abstractions.Choose your approach based on project size and maturity - frameworks might work for prototypes, while modular libraries are better for long-term projects.IntroductionIn this episode of the No Silver Bullet podcast, we discuss frameworks in Go and when they're useful or problematic.We talk about why the Go community generally avoids frameworks compared to other languages, and how small, modular libraries are often preferred in Go development.We share our experiences with frameworks across different projects, including tradeoffs between productivity and long-term maintenance.NotesModel-View-Controller (MVC): Pattern first described in the 1970s for Smalltalk, still widely used today.Unix Philosophy: https://en.wikipedia.org/wiki/Unix_philosophy: design concept created by Ken Thompson (also a Go creator) promoting small programs that do one thing well and work together.When to avoid DRY in Go: https://threedots.tech/post/things-to-know-about-dry/Watermill: https://watermill.io: Our event-driven library for Go designed to not be a framework.Repository Pattern: https://threedots.tech/post/repository-pattern-in-go/: Our blog post that is still relevant and frequently referenced.tdl: https://github.com/ThreeDotsLabs/cli and pq: https://github.com/ThreeDotsLabs/watermill/tree/master/tools/pq - the CLI tools we mentioned.Clean Architecture: https://threedots.tech/post/introducing-clean-architecture/: The topic of our next podcast episode, a design approach that helps maintain separation of concerns.Wild Workouts: https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example : Our example Go codebase demonstrating clean architecture.   The Best Go framework: no framework?: https://threedots.tech/post/best-go-framework/QuotesThe happy path is easy enough, but the happy path is usually not the hard part of software. We often overvalue how much effort the boilerplate requires. - MiłoszFramework knowledge tends to become out of date. You can spend days or weeks learning something about a framework, but it can be outdated. And if you switch to another programming language or company, a lot of effort that you spent to learn stuff will be just wasted. - RobertIt's more important to learn even-driven architecture because you learn the theory behind it and how it works in general - it transfers better to whatever you will do later. Focus on timeless skills like how to split modules in your application, how to make it decoupled, how to write business logic so it's easy to read and modify. - MiłoszThe Go language is heavily influenced by Unix philosophy - write programs that do one thing and do it well, write programs that work together. It's visible in Go's standard library. This is why Go promotes building independent components that you can connect together. - RobertYou need to be careful not to go too far with foundations. It's better to start with some modular libraries, have some reasonable setup in place, but don't go too crazy with it. Most of the time you'll need to refactor the project anyway, whatever you do, because it can change drastically. - MiłoszOne big decision at the beginning may cost you six months of work later. Understanding if something is tightly coupled to your application is simple - just think about how easy it would be to remove it. - RobertFull episode notes: https://threedots.tech/episode/when-you-should-not-use-frameworks/
Quick TakeawaysHigh-quality code is mainly about keeping good iteration speed over time - can you add features without breaking what works?Not all code needs to be high quality - focus your efforts on the code that's most important, changes often, or creates the most value.The right time to refactor is after you know your product has value, but before technical debt gets too big - make small improvements bit by bit.Architecture decisions need more thought than other code quality factors since they're hardest to change later - ask "how hard would this be to remove?"Balance between MVP and quality depends on your situation - for experiments, cut corners on purpose; for critical systems or enterprise products, focus on quality from the start.IntroductionIn the first episode of No Silver Bullet live podcast, we talk about the balance between writing high-quality code and taking shortcuts.Based on nearly 20 years of working together on various projects, we discuss when it makes sense to move fast rather than aim for perfect code, and how to avoid technical debt that can kill your project.We focus on making mindful engineering decisions instead of blindly following rules like "always do X" or "never do Y".Different situations need different approaches to code quality.Notes"No Silver Bullet": The classic 1986 paper by Fred Brooks that our podcast name references, discussing how there's no single development that will solve all software engineering challenges.Our Learning Platform: /learn: The project we discussed that started as an MVP and was later refactoredArchitecture Patterns:Pareto Principle: The 80/20 rule we mentioned - often 20% of the code creates 80% of the valueFuture Episodes:Development Practices:QuotesQuality is not about some kind of elegance of code because it's just an artificial thing. It's not really helpful for your team if it's pretty. - MiłoszOften you have in the code places that you don't touch often or it's not earning a lot of money... Ask what are the places that we're changing the most often? What are the places that are creating most of the value? - RobertI remember one project when we started applying Domain-Driven Design... Everyone loved it in the team... But what I also remember is that it had no paying users and we had to shut it down. - MiłoszIf you did your dirty POC, don't miss the time when you should clean it up, because it's easy to go into a spot where it's no longer possible to do that. - RobertAfter working with [legacy systems] for long enough, you might think, 'I've had enough of this, I won't allow my next project to rot like this one.' It sounds like a good idea, but it can also be a trap. - MiłoszSometimes it can be even the opposite. Having everything super consistent can actually be worse than having inconsistent things, because keeping this consistency requires effort... Sometimes it requires you to use an approach that is not optimal just because 'we're doing it consistently.' - RobertA useful mental model here is to care about the useful product first, not the technical design, which of course is important, but I think it's easy to overvalue it. - MiłoszTry to find some places where you can do refactoring in one week... Often you don't need to rewrite an entire service. You can just do some refactoring in the code and iterate on that. - RobertEpisode notes and summary: https://threedots.tech/episode/when-to-write-low-quality-code/
Comments