5 ms·
I'll give you the cheat sheet: - Good design is a single idea pervaded throughout. - More generally, your goal should be to minimize surprise. - If your syst
by CSMastermind 5mo ago
I'll give you the cheat sheet:
- Good design is a single idea pervaded throughout.
- More generally, your goal should be to minimize surprise.
- If your system allows it, people will do it.
- Everyone will not just. If your solution starts with "if everyone will just..." then you don't have a solution.
- Isolate the parts of your system that transform data from the ones that use it. Data models outlive code.
- Coupling is the root of most evil.
- Versioning is inevitable.
- Make state explicit.
- Every piece of information should have a single source of truth.
- You should spend more time thinking about naming things correctly.
- If testing is difficult, the design is wrong.
- You will regret every undocumented decision.
- Communication is a tax that you should justify before paying it.
Remember that the job of an engineer at any level is to use rules of thumb to solve problems for which there is incomplete information.
- laszlojamf 5mo agoI'd add - data migrations are inevitable and should be planned for (corollary of versioning) - planning is good, sometimes you just have to try things out - everything costs money. Designing without costs in mind will force hard choices down the line
- AnimalMuppet 5mo agoI'll add another to that: - Code lives longer than you expect. You forget sooner than you expect. Make a readme/architecture overview/theory of operation document. Put more in it than you think is needed. Check it in with the code.
- john_builds 5mo agogreat list! ty
- thedetailsguy 5mo agoThanks for sharing! Really agree with #2 even though ultimately - we can only minimise rather than totally eliminate.
- mwexler 5mo agoCan you explain the last one? What types of communications are you suggesting an arch would avoid? Otherwise, a very wise list!
- theteapot 5mo agoCompletely agree. Had me until the very last point. WTF. Communicate.
- murkt 5mo agoThe last one is about involving less people. You don't have to read it as "shut up and keep your thoughts for yourself". I read it more like "Do we really need to have six people working on this feature/present in this call?"
- gavmor 5mo agoI wonder if they don't mean "between systems".
- collabs 5mo ago> Communication is a tax that you should justify before paying it. I thought it meant like keep things as local as possible. Like within the same process, within the same machine, avoid going through the network because staying on the processor is always the fastest, going to ram is next fastest, and if you need to communicate across the network it is always slowest
- electrosphere 5mo agoGood list! One addition? * start with a modular monolith
- perkovsky 5mo agoYes. I’d add: design the module boundaries before splitting deployment. A modular monolith still forces you to name ownership, data boundaries and invariants, but without making every mistake a networking/ops problem.
- munk-a 5mo agoI don't want that to be limited to just architecture though - proper initial factoring of a software product is invaluable at the large and most microscopic level.
- rafael-lua 5mo agoThis one has yet to enter my head. I read Martin Fowler's related article* a few times, and it is sound... But I still can't see microservices being so "complex" to be so avoided early on. Especially in 2026, with the level of IaC and pipelines we have. I might be naïve, but I just don't get it. (*) https://martinfowler.com/bliki/MonolithFirst.html https://martinfowler.com/bliki/MonolithFirst.html
- electrosphere 5mo agoIt depends on your workload etc - but there are TONs of advantages for the monolith approach when you factor in dev environments, access to good data for testing/development, dependencies on components/libraries/services etc. Note: I'm saying microservices CAN be the right answer, just not always and we should over-engineering. sauce: I work as a product engineer where we use microservices extensively. I have also worked on monoliths. Preferred working on the monoliths overall I think.
- tristor 5mo agoAs a corollary to > Communication is a tax that you should justify before paying it. > Every piece of information should have a single source of truth. - Do as much as possible on a single system and minimize sharing state. - Recognize that every system is distributed, it's just a question of how and where. One of the biggest ills I observe with most modern software systems is that we've gone full tilt towards things like microservices which require synchronizing state across multiple interdependent parts. Regardless of how clean the abstractions or how well contracted the APIs, doing all of that copying and state synchronization is going to result in problems: performance problems, cost problems, and synchronicity problems.
- ofrzeta 5mo ago> - Make state explicit. Don't save the same state in more than one place.
- munk-a 5mo agoYou can and you must replicate state in a complex system. The key is that state should always have an explicit owner and other carriers of that state should understand that their version of the state has some inherent lag or inconsistency off the true state. There's a reason caching (and it's invalidation) is one of the two hardest problems in computer science.
- RobRivera 5mo agoI wish I could buy you a beer, as this is very validating. I have been building a video game for over a year. But more importantly I have been building a sustainable engine with a distinct data pipeline, resource rendering/management layer fully decoupled, explicit catalogueing of viable state transformation modes, and honestly, most of your list is absolutely applicable. Even tho it is solo, the constraints of my engine guides forgetful me to 'this is the way to add this weird new feature that arose from testing because reasons' without having to have a tome of 'if you want to build a new sound effect that executes at a particular state transformation multiple times, here is how you do it' Cheers
- embedding-shape 5mo agoAh, the infamous "I believe I'm building a video game, but in reality I'm building a video game engine and I don't realize it yet" every game developer goes through at least once, god speed to you :)
- embedding-shape 5mo agoMissing the single most important thing, that people seemingly are purposefully trying to avoid nowadays for some reason: - It depends No solution I've come across, is a solution for everything, everywhere. It's almost always context dependent, and something that is right in one place, can be utterly wrong in another, and there is no universal truths regarding design and architecture. The more flexible you can be with "right tool for the right job", the easier time you'll have designing, because you're no longer trying to shoehorn in things that are "right" and "correct". > - You should spend more time thinking about naming things correctly. This should almost be on the list twice too, the amount of people who couldn't care less about naming, is so damn high, but if people just cared a tiny bit, it'd solve so much future confusion. Whenever I get pulled into helping a "legacy project" or whatever, establishing a "true vocabulary" based on what people actually call things, is the very first thing to do, because it always uncovers that people been talking about different things the entire time.
- rafael-lua 5mo agoTo be honest, "It depends" is also exhausting. I have some defaults that I push until the breaking point, though I apply this more often on personal projects. DDD is an exception, as even professionally I will vomit DDD concepts as solutions for literally anything. "This could have been an aggregate", I will say like a mantra.
- abustamam 5mo agoI found I did pretty well in the interview stage whenever I'd start an answer with "it depends" Of course, I'd have to back that up with assumptions and goals and such, but I think that's the point. X technology is great at Y but not so great at Z, so if you want to optimize for Z maybe don't use X.
- ivanjermakov 5mo ago> If testing is difficult, the design is wrong Or domain/IO is cumbersome. Think videogames.
- embedding-shape 5mo agoThat's one of the few places where creating abstractions makes a lot of sense, especially in video games. You really, really want as much to be automated tested as humanly possible, because the user/player surface ends up enormous and testing gets "expensive" much quicker than a typical SaaS.
- nullsanity 5mo agoNope, that's just the design being wrong. Factorio has no issues with testing. It doesn't matter if nobody is interesting it fixing it, video games have been and almost always are written like shitty single use code, (Even when it's not) so it shouldn't surprise anyone that proper test harnesses aren't available in engine.
- dmitrijbelikov 5mo agoGood, really good. But not real. You can achieve such ideal only without human which makes no sense.
- bfivyvysj 5mo agoGreat list but I dunno about coupling being evil, it is literally the only place anything important happens.
- rafael-lua 5mo agoCoupling is evil, as it is the main root of all the nastiest problems I have found in many projects I worked on. It is the main cause of "throw it all away and start greenfield" in my experience. I mean, it is important and needs to be done right, but it is yet evil.
- kachnuv_ocasek 5mo agoWhat exactly do you mean by “make state explicit”?
- embedding-shape 5mo agoDepends on the language I'd say, but overall, try to keep state in as few places as possible, and make it more obvious when it's being used. Modern example would be Rust defaulting to immutability for variables, and makes it very clear when you should expect that this variable actually carries state rather than just a value, by prefixing it with `mut `. Other languages might make it the other way around, making constants/read-only variables stick out, and defaulting to "hiding that they have state" basically.
- munk-a 5mo agoMinimizing side effects (e.g. functional programming) is a good habit in general. But when we're talking at the scale of a company's entire software solution state is the enemy of everything that is good. You never want to work on a platform with 500k lines of code any of which could mutate variable state at will and the more you can isolate or simplify state usage the better your life will be.
- topaz0 5mo agoIt's also the enemy of evil things your company does.
- CSMastermind 5mo agoThe behavior of every valid state should be defined and obvious to anyone making changes. Invalid states should be impossible to represent. All software is actually a state machine and almost all complexity comes from unmanaged state transitions. Especially if you have errors, concurrency, async actions, and distributed systems. The most important task any software architect has is defining how you know what is true about the system. Consider a few examples: await sendEmail(); await saveUser(); That's obvious right? Well what happens when saveUser fails and some retry sends a duplicate email? --- If you're relying on a webhook to let system A know when system B has changed things, what happens when that failed and now the two systems are out of sync? This is why event-driven architectures often work well: they externalize state transitions. --- A classic React problem, you define state like this: ``` const [loading, setLoading] = ... const [error, setError] = ... const [data, setData] = ... const [isRefreshing, setRefreshing] = ... ``` Now there's all sorts of invalid states you can be in. Like "loading true, data true, error true". ** You should strongly prefer: state machines, lifecycle enums, event logs, immutable events, append-only histories, strict versioning, idempotency keys, and explicit workflow stages. You should avoid like the plauge: hidden globals, timing assumptions, inferred behavior, synchronization, side effects, boolean explosion, mulitple sources of truth. Event sourcing, CQRS, Redux, Kafka, workflow engines, finite state machines, transactional outboxes, CRDTs, database normalization they all do the same thing _control where the state lives_.
- avgDev 5mo agoGood list, although things get weird when you are limited by some legacy software/database.
- munk-a 5mo agoYou are always limited by some legacy software/database. If you're not limited by some legacy software/database initially then you will have such a large problem scope that you'll create your own legacy software/database internally. I'll grant that if your problem is really simple and straightforward you can sometimes just build an ideal greenfield solution that's perfect and wonderful - but those problems are rarely the problems that are profitable to solve.
- miki123211 5mo agoI don't agree with all of these, but I'll add a couple of my own: - The ultimate goal of software is to solve the immediate problem at hand. The secondary goal of software is to solve likely future problems with as little work as possible. Any bad design which is better on those goals than a good design is actually a good design. - Make your interfaces easy to use correctly and hard to misuse. Think of how people unfamiliar to the project will interact with them, make the obvious way be the correct way. - Correct code should be easy to write; suspicious code should stand out. - Shift bugs left. - Fixing a bug class is better than fixing a bug. - Interfaces are harder to change than implementations. An ugly implementation is ok if it has the correct interface. - Use comments and documentation to explain why the code is the way it is. If it feels like there's a simpler way to do it, but that simpler way wouldn't actually work due to a constraint some people may be unaware of, document that. - Don't repeat yourself; when it comes to data. If you store a single fact in multiple places; those places will inevitably get out of sync, and that causes bugs. - There's a cost to straying off the well-trodden path. Don't be afraid to do so when it's truly worth it, but don't underestimate that cost. Worse (boring) technology is often better technology. - Think in terms of expected value. Think not "Is this thing worth doing?", but "Is this thing worth doing, compared to the other things we could be doing instead?" - Even if you think you're smarter than everybody else, intelligence isn't always enough, some problems can't be discovered until they happen. Other people have worked longer on this problem than you have. Learn from their mistakes. - Friction is the silent killer.
- dirtbag__dad 5mo ago> The ultimate goal of software is to solve the immediate problem at hand. The secondary goal of software is to solve likely future problems with as little work as possible. Not really. Today I learned of a story where a vibe coding PM deployed a vercel app, which was in its entirety a single react component that serves a banner, that was embedded via an iframe into an entirely separate web app, whose repo they have full access to, because they needed to get a modal component out the door, but were blocked because they “couldn’t upload the image to s3.” (They could just use the public/ or assets/ directory and have the cdn pick it up, too.) People do the most insane things when they are tight on time and don’t have the intuition to do a bit of research up front. If you ask them, they’re solving the immediate problem at hand. If you ask anyone else who interacts with their work, they’re disrupting our days/weeks/months/years when we have to step through systems that don’t know any better. If you have the muscle to plan ahead, even a little bit, which is usually just following the same pattern over and over again, then you are fine. In this world you are still optimizing for the now problem but you have also solved, to some and ideally a better degree, some future problems for free
- pelasaco 5mo agothen comes the order from your manager: "Put it in production and we can work through this list later".. or even worse, now with Claude, managers are putting stuff "Quick and Dirt" in production and you have to maintain it.. sadly true story..
- munk-a 5mo agoThe job of an architect and good technical management is to teach non-technical management the true costs of their decisions. It's not an easy job by any measure and LLM driven know-nothings in management can make it far more frustrating. But, you need to make sure you're capturing the true[1] costs of decisions to the best of your ability including externalities. 1. You're allowed to lie for expediency, but make sure you never delude yourself into believing your own lies.
- munk-a 5mo agoI work in this space as well - I know that my particular work is much less abstract (we're modeling the healthcare industry) than some others. But as a designer of the system you must understand the industry and while you don't need to embrace their terminology and modeling habits fully, you must understand their rationale and how they view the data set. There are some places where we've intentionally simplified complications of the healthcare market to eliminate needless (to us) over definitions and provide a more unified modeling. But these changes took significant comprehension of the problem area to make with confidence. > You should spend more time thinking about naming things correctly. On this point in particular. Names never die - that's a lie, occasionally they do, but it takes an extreme amount of effort to enforce a renaming. It really is worth spending a big bulk of time letting SMEs stew with naming proposals to make sure you bases are covered. You can force through a few concepts but your business wing (sales, marketing) will constantly put pressure on you towards industry terminology and force your model to adhere to the current view of the industry. If you decide to break with that that break must be decisive and obvious in intent. Oh, the single biggest attribute of software to emphasize is maintainability. How much will this cost to build is one question - how much will this cost to run (not just infra but compounding feature requests and code refactoring and maintaining third party software versions etc...) is the far more impactful.
- keithnz 5mo agoI don't actually think this has much to do with software architecture. Except maybe the "Isolate parts of your system..." In fact the article itself didn't really seem to be too coherent on software architecture. I think the 4+1 view of software architecture is a good conceptual way to think about things (minus all the UML). It's not a complete picture but it addresses the bigger picture. The book series Pattern-Oriented Software Architecture is pretty good and outlines a number of architectures people tend to land on. One stage Grady Booch was working on a handbook of software architecture, but seems to be kind of dead ish, but he did have a mailing list where he'd go to companies or look at large open source projects and look document big system architectures (and some smaller scale), not sure where you can see this anymore. All of these things are worth diving into if you want to learn about software architecture. You can see these architectures are built for different focuses, things like scale, safety, performance, interoperability, failsafe, etc. i.e. there are very specific goals of an architecture with very real tradeoffs.
- dirtbag__dad 5mo agoGood architecture is not about the patterns you pick. It’s about having a team dev cohesively on any pattern(s) - programmatically enforce your style with in-house linters and scaffolders. Be highly opinionated - if you # ignore anything, leave a comment explaining why - use bdd so your cases are human readable and must update with the code (unlike a stale comment) - follow the same pattern in all your services (I love hexagonal design for backend). If you break from the design have a good reason for it - COMPOSE your code. It’s almost always the case that two endpoints or jobs or whatever happen to have a few things in common, but differ wildly. Don’t get DRY, just import those things in each spot and don’t entangle them with each other, it’s too confusing - scope your interfaces tightly. No optional params unless the domain is actually optional. Make separate jobs or endpoints for different configs. I can’t figure out wtf all your configs were from 4 years ago and neither can you - code is default testable with DI/DIP - always run tests running real infra like testcontainers - cement ci/cd as your guardian. Employ every linter you can. - hammer workarounds with comments in the code - break all systems into small problems and nothing is challenging technically (though the domain might be!) - work with less people on your code. It’s easier to stay aligned and follow the same patterns - don’t expect anyone to figure it out. You need to champion your changes. Do code reviews, hold their hand, show them the way - clear out mundane items like installfests, local auth, server reloads. Slow dev pisses people off - finally, and most important, employ an org policy for review. Don’t let shit fall through the cracks unknowingly. It compounds. You have skills now as a quick and dirty to eval and enforce. Use them!
- kreelman 5mo agoAtlassian and Google require all of their dev hires to be "up to scratch" on architecture. They don't hire system architects at all, as far as I understand. I wonder sometimes if the role of architect in a business might be about having a group/team wide senior person who - Knows how to architect systems very well and can share that knowledge with the larger group and more junior devs - Is a kind of high level business analyst who can speak "business, stuff that makes money" and "dev, how it's done" to each of those groups effectively Does Google/Atlassian miss out on things by not having system architects? ... Or can they achieve what's needed by having sensible team rules (so architecture gets followed) and relatively standard reusable architectures (so it's not too hard to adapt to a given business solution). I'd be genuinely interested to know. My aspiration was to become an architect, but I now wonder if this is the right way to go.