I think this really down plays the value of mental model or strategies for organizing code. Take a compiler: often described as a sequence of transformations on an AST, taken to the extreme in the nanopass framework. That's a really useful mental model, and you can extract that model and apply it in other contexts. For example, many business applications are a sequence of transformations on JSON. So they're basically compilers. That can be good architecture in the right situation.
You don't have to call a sequence of transformations a compiler. You can say your AST is an algebraic data type, and your transformations are folds (or structural recursions; same thing). Now you have an abstract model that isn't tied to a particular application, and you can more easily find uses for it.
If you know a bit of maths you might wonder about duals. You will find codata---objects---are the dual of algebraic data. Ok, now we're programming to interfaces. That's also useful in the right context. What's the dual of a fold? An unfold! So now we have another way of looking at transformations, from the point of view of what they produce instead of what they consume. At this point we've basically reinvented reactive programming. And on and on it goes.
You can find most of this in the literature, just not usually presented in a compact and easy to understand form.
(Note, the above description is very quick sketch and I'm not expecting anyone to understand all the details from it alone.)
I would like to spend my time more on gaining a mental model of the projects I work on, but I get very demotivated if I start disliking things like the programming language, certain arch. Choices or anything that gets too complex that doesn't seem like its worth my time
It's heavily dependent on the project, but I feel like working as a "fullstack dev" kind of removes the fun of programming. I'm already spending 40 hrs a week looking at the most dull project I can imagine
Analysis paralysis is a danger always - don't aim for perfect, but aim for flexible. Make sure the decisions you're spending the most time on are the most important.
A well designed data structure is far more impactful on performance and maintainability than framework, language or platform.
Personally, I work alongside ADHD every day so I need to constantly push to make sure that progress is happening which means picking unimportant decisions and resolving them to focus the remaining open problem space on the decisions that require a lot of careful thought.
The recommendations are often very good, for example Ousterhouts A Philosophy of Software Design, but seem to be on software development in general, not actually software architecture in particular.
For that, I would recommend the classic texts, such as Software Architecture: Perspectives on an Emerging Discipline (Shaw/Garlan) and really anything you can find by Mary Shaw. Including more recent papers that explore why the field of software architecture did not go the way they foresaw, for example Myths and Mythconceptions: What Does It Mean to Be a Programming Language, Anyhow? or Revisiting Abstractions for Software Architecture and Tools to Support Them
More practically: look at why Unix pipes and filters and REST are successful, and where they fall down and why. Hexagonal architecture is also key.
And a plug for my own contribution, linking software architecture with metaobject protocols as a new foundation for programming languages and programming: Beyond Procedure Calls as Component Glue: Connectors Deserve Metaclass Status. An answer to Mary Shaw's Procedure Calls Are the Assembly Language of Software Interconnection: Connectors Deserve First-Class Status.
Answering the question: if procedure calls are the assembly language, what might a high level language look like? And also maybe that software architecture might have a brighter and more practical future ahead of itself.
In this vein, I really recommend "Architecture of Open Source Applications."[1] It's a book series where you learn architecture by example, with each chapter written by a maintainer of the project in question. This lets you learn not only what the architecture is, but what are the constraints that shaped it, usually history and changing project visions.
Not all chapters are equally good or equally interesting, that's the curse of a multi-author book, and all of them are dated, but I think the book is worth reading nonetheless.
Software design/architecture is a strange beast. It feels that if you want to learn it, you should spend time in legacy systems and large codebases of rewrite a project 3 times to explore counterfactuals. A lot of books on the subjects are abstract and give such simple examples, they are useless.
The abstraction and generic design is part of the point, though. It's not really about writing concrete examples, but experiences and how to be ready to structure based on those experiences.
I think there is huge space for architecture case studies that help a non coder learn how to critique llm architecture decisions.
I’m a NP - lots of learning came in clinical rotations where you see real life situations and how they are addressed. I want something like this for software architecture.
The closest I’ve seen is the open source case study books referenced previously but these are older.
I’d like to be able to see explanations at various layers of abstraction about why certain decisions are made or not.
Wanted to say thanks for posting this. I found the NDC talk interesting and well presented. Will try to read more about it and, as an exercise, try applying it to my own solutions to see what insights I could gain.
Learn by doing? Certainly helpful. But I feel like the real secret is to work with others who are good at software architecture. You can learn very efficiently that way.
The 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:
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_.
I 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.
> - 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.
I find this principle to hold only superficially, and once subjected to scrutiny it breaks down.
There is a reason why Don't Repeat Yourself (DRY) was superceded by Write Everything Twice (WET). There were far too many junior devs swearing nonsense such as how a DRY abstract class that is inherited by two concrete classes is simpler than two classes or even functions that do mostly the same thing. These junior devs rolled out convoluted abstraction layers in name of DRY that would make enterprise code blush. Moreover, these simplistic yet fundamentalist DRY tricks introduced coupling between components that are similar at a superficial level but semantically held no relationship.
Instead of rolling out premature abstractions, code must allow duplications. Copying a class poses a far lower risk than dumping a sea of inheritance with a bunch of abstract classes and interfaces and generics. The job of a software architect is to save developers from themselves, and minimizing code is such a way.
then 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..
I 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.
I 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.
Good 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!
Atlassian 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.
This guy knows exactly what he's talking about. However, it feels more like a "if you know, you know" rather than relaying the experience/data he's accumulated.
Let me just say: A lot of people think architecture is how do you build a very complex system with tons of moving components (databases, queues, scaling, reduandancy, failover, dozens of services). I think expert achitecture is being able to answer how do I correctly solve the problem using the fewest of those.
Genius-level engineering is inventing the zipper, usable anywhere, lightweight, cheap, sturdy, simple to make.
(I believe he's alluding to this by saying companies follow conways laws and essentially socially construct a lot of work to match up with the number of teams, hence creating a completely unnecessary nightmare of complexity just for the social incentives)
As relates to Systems Engineering: Software architecture is like plumbing architecture. It's certainly very important, but you don't live in plumbing pipes, you live in a house with plumbing. If your plumbing doesn't take the rest of the house into account, it could be a very expensive fix.
I think that words like "clean code" or "beautiful code" does help juniors to learn best practices of software architecture.
- Junior asks to senior: what did you we use an ORM ?
- senior answers: because it's cleaner.
- junior: ???
I prefer when people are able to define a clear list of objectives:
- maintenable;
- performant, scalable;
- efficient;
- resilient;
- observable;
- testable (and tested);
- secured;
- readable for new devs that come on board.
Each criteria balance the others, and the more we add criteria the more it helps to make good choice when we hesitate. It is also meaningful for people outside of the dev team, we can reach an agreement with the customer so he knows what he pays for.
"maintenable" can be also defined, since a project is mostly in maintenance mode during its lifetime (which means the project is successful, which is good !). The ability to incorporate new features without breaking architecture or even without breaking a single method signature is a good starting point.
Being super careful with abstractions. Here someone wrote something like that: "abstraction often hides how what you want is simple". True, ORM I am looking at you. In most case data should be threaten as first class citizen, it also fosters good collaboration with the DBA.
Thinking beyond "the happy path" without falling in premature optimization is also a challenge. A nice one, once again to it avoids to go head first in implementing a good idea without considering drawbacks.
I also tend to imagine that the one that will work on my code had a very bad day so it must be pleasant to read what I wrote. Comments here and there, locale variable here and there even if they can be avoided, variable naming, etc...
Being selective about frameworks. They are good servant but bad leaders. "Be an engineer, not a frameworker" says an article.
Modular frameworks are worth their weight in gold when compared to "highly opinionated frameworks". I don't mind something that's highly opinionated - and highly opinionated is an excellent trait for a library or tool. That lib/tool likely has a large amount of domain expertise in the particular area it operates in.
When it comes to frameworks - highly opinionated tends to get corrupted into "We have this hammer and suddenly everything looks like a nail" - it doesn't always happen and it doesn't always bite you but when orthodoxies are taken from one domain and applied broadly you run the risk of some of the justifications for that orthodoxy being domain specific and being violated in the wider context.
So, when it comes to frameworks, I like modularity where I have options to plug in a different ORM or persistence integration layer - where I can swap out the router - or the validator - or any other component that proves a poor fit for our problem. It is especially valuable if there are multiple paradigms expressed in alternatives within the framework's ecosystem since you may find a pre-baked tool that mostly works where you can choose an option with shortcomings that are clear and addressable if ever the need arises.
To support maintainability it's very important to fight against NIH-ism, that is a constant danger that can soak up resources at an alarming pace - but it's also important to realize that there are some components that you will greatly benefit from tweaking or taking full ownership of with the most difficult problem being trying to figure out which is which.
I appreciate deeply that your list put maintainability first - that's my opinion as well!
Immediately favorited as this article and the comments underneath are great reference points and reminders. (Even as someone that's been doing this for awhile)
A lot of people seem to lean too much on Conway's Law. It takes social organization as primary instead of itself shaped by the nature of a problem. Maybe the reason Sales and Engineering are different departments is because they are different things.
47 comments
[ 2.7 ms ] story [ 69.4 ms ] threadYou don't have to call a sequence of transformations a compiler. You can say your AST is an algebraic data type, and your transformations are folds (or structural recursions; same thing). Now you have an abstract model that isn't tied to a particular application, and you can more easily find uses for it.
If you know a bit of maths you might wonder about duals. You will find codata---objects---are the dual of algebraic data. Ok, now we're programming to interfaces. That's also useful in the right context. What's the dual of a fold? An unfold! So now we have another way of looking at transformations, from the point of view of what they produce instead of what they consume. At this point we've basically reinvented reactive programming. And on and on it goes.
You can find most of this in the literature, just not usually presented in a compact and easy to understand form.
(Note, the above description is very quick sketch and I'm not expecting anyone to understand all the details from it alone.)
Shameless self promotion: the book I'm writing is about all these concepts. You can find it here: https://functionalprogrammingstrategies.com/
It's heavily dependent on the project, but I feel like working as a "fullstack dev" kind of removes the fun of programming. I'm already spending 40 hrs a week looking at the most dull project I can imagine
A well designed data structure is far more impactful on performance and maintainability than framework, language or platform.
Personally, I work alongside ADHD every day so I need to constantly push to make sure that progress is happening which means picking unimportant decisions and resolving them to focus the remaining open problem space on the decisions that require a lot of careful thought.
For that, I would recommend the classic texts, such as Software Architecture: Perspectives on an Emerging Discipline (Shaw/Garlan) and really anything you can find by Mary Shaw. Including more recent papers that explore why the field of software architecture did not go the way they foresaw, for example Myths and Mythconceptions: What Does It Mean to Be a Programming Language, Anyhow? or Revisiting Abstractions for Software Architecture and Tools to Support Them
More practically: look at why Unix pipes and filters and REST are successful, and where they fall down and why. Hexagonal architecture is also key.
And a plug for my own contribution, linking software architecture with metaobject protocols as a new foundation for programming languages and programming: Beyond Procedure Calls as Component Glue: Connectors Deserve Metaclass Status. An answer to Mary Shaw's Procedure Calls Are the Assembly Language of Software Interconnection: Connectors Deserve First-Class Status.
Answering the question: if procedure calls are the assembly language, what might a high level language look like? And also maybe that software architecture might have a brighter and more practical future ahead of itself.
And when you try to prevent that IoC from leaking into the domain too much, the design often starts to look like hexagonal architecture.
Programming often feels like inventing a new form, but in the end we tend to converge on the shapes that previous programmers already discovered.
Not all chapters are equally good or equally interesting, that's the curse of a multi-author book, and all of them are dated, but I think the book is worth reading nonetheless.
[1] http://aosabook.org/
I’m a NP - lots of learning came in clinical rotations where you see real life situations and how they are addressed. I want something like this for software architecture.
The closest I’ve seen is the open source case study books referenced previously but these are older.
I’d like to be able to see explanations at various layers of abstraction about why certain decisions are made or not.
Read tip: Simplify IT - The art and science towards simpler IT solution https://nocomplexity.com/documents/reports/SimplifyIT.pdf
As well as the book (https://leanpub.com/residuality) the author has one on the philosophy of architecture (https://leanpub.com/architectsparadox).
- 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.
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_.
- 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.
I find this principle to hold only superficially, and once subjected to scrutiny it breaks down.
There is a reason why Don't Repeat Yourself (DRY) was superceded by Write Everything Twice (WET). There were far too many junior devs swearing nonsense such as how a DRY abstract class that is inherited by two concrete classes is simpler than two classes or even functions that do mostly the same thing. These junior devs rolled out convoluted abstraction layers in name of DRY that would make enterprise code blush. Moreover, these simplistic yet fundamentalist DRY tricks introduced coupling between components that are similar at a superficial level but semantically held no relationship.
Instead of rolling out premature abstractions, code must allow duplications. Copying a class poses a far lower risk than dumping a sea of inheritance with a bunch of abstract classes and interfaces and generics. The job of a software architect is to save developers from themselves, and minimizing code is such a way.
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.
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.
- 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!
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.
https://aosabook.org/en/
Let me just say: A lot of people think architecture is how do you build a very complex system with tons of moving components (databases, queues, scaling, reduandancy, failover, dozens of services). I think expert achitecture is being able to answer how do I correctly solve the problem using the fewest of those.
Genius-level engineering is inventing the zipper, usable anywhere, lightweight, cheap, sturdy, simple to make.
(I believe he's alluding to this by saying companies follow conways laws and essentially socially construct a lot of work to match up with the number of teams, hence creating a completely unnecessary nightmare of complexity just for the social incentives)
"maintenable" can be also defined, since a project is mostly in maintenance mode during its lifetime (which means the project is successful, which is good !). The ability to incorporate new features without breaking architecture or even without breaking a single method signature is a good starting point.
Being super careful with abstractions. Here someone wrote something like that: "abstraction often hides how what you want is simple". True, ORM I am looking at you. In most case data should be threaten as first class citizen, it also fosters good collaboration with the DBA.
Thinking beyond "the happy path" without falling in premature optimization is also a challenge. A nice one, once again to it avoids to go head first in implementing a good idea without considering drawbacks.
I also tend to imagine that the one that will work on my code had a very bad day so it must be pleasant to read what I wrote. Comments here and there, locale variable here and there even if they can be avoided, variable naming, etc...
Being selective about frameworks. They are good servant but bad leaders. "Be an engineer, not a frameworker" says an article.
When it comes to frameworks - highly opinionated tends to get corrupted into "We have this hammer and suddenly everything looks like a nail" - it doesn't always happen and it doesn't always bite you but when orthodoxies are taken from one domain and applied broadly you run the risk of some of the justifications for that orthodoxy being domain specific and being violated in the wider context.
So, when it comes to frameworks, I like modularity where I have options to plug in a different ORM or persistence integration layer - where I can swap out the router - or the validator - or any other component that proves a poor fit for our problem. It is especially valuable if there are multiple paradigms expressed in alternatives within the framework's ecosystem since you may find a pre-baked tool that mostly works where you can choose an option with shortcomings that are clear and addressable if ever the need arises.
To support maintainability it's very important to fight against NIH-ism, that is a constant danger that can soak up resources at an alarming pace - but it's also important to realize that there are some components that you will greatly benefit from tweaking or taking full ownership of with the most difficult problem being trying to figure out which is which.
I appreciate deeply that your list put maintainability first - that's my opinion as well!