If you give that team that structure, it seems to me you're highly unlikely to end up with beautifully bounded context and highly likely to end up with even more of a mess than you started with.
If you give that team that structure, it seems to me you're highly unlikely to end up with beautifully bounded context and highly likely to end up with even more of a mess than you started with.
Daylight is the best antiseptic.
It's not giving them a microservices architecture and Docker that solves problems. It's expecting them to use it that causes problems to be solved, or at least formally identified.
I do a lot of root cause analysis whether my boss asks for it or not. Human factors are almost always in play. A significant source of bugs that make it into late QA or even production are caused by wishful thinking about the scope and impact of code changes. Things that should have been red flags are brushed off because you can't prove that their code caused the problem, and as long as there is reasonable doubt about the source, some people won't look at code they already declared done.
When the code is developed and tested in an isolated system then the only source of state changes on that system were caused by the new code. It takes a real set of brass ones or an extremely dense skull to deflect concerns about problems seen on a system that only contains your code changes. People either shape up or get labeled as untrustworthy. The former result is preferable, but at least with the latter you get predictability out of the system.
Microservices remind me of a particular C++ protocol implementation I wrote as a novice programmer. Since the protocol was structured, I had high level classes broken down into simpler classes. e.g.
virtual class Reader {
void read(Buffer *buf);
};
class TCharacter: public Reader {
TString *name;
TList<TStat> *stats;
...
};
class TStat: public Reader {
TWord32 *remaining;
TWord8 *percent;
};
class TWord32: public Reader {
uint32_t v;
};
class TWord8: public Reader {
uint8_t v;
};
And on, and on, and on, and on ...I thought it was cute that down to the simplest PODT, every type satisfied a well defined `Reader` interface. I hand wrote many constructors, deconstructors, read and several other interface definitions for each type.
Elegant, arguably. Inefficient, yes. Overly complex, undoubtedly.
Every argument for microservices has in some way reminded me of this code, with it's well defined interfaces and overly verbose implementation.
My current strategy is to utilise the crap out of language features to safely achieve what I want in the minimal amount of code possible. In C++ this would be done by initialising the protocol structure classes straight out of the memory buffer, using #pragma pack as required. No error prone constructors / deconstructors / read() required.
Haskell and Scala are two languages I use a lot, and each have powerful type systems which I can use to prevent myself from doing something stupid. Programmable macros are great for removing boilerplate. Want less technical debt? Write less code.
In my own controversial opinion, if somebody can't work out how to modify my code because they can't work out how to correctly update the type definitions, that person has no business modifying my code. Problem solved! Interfaces are well defined and modifiable only by people who truly understand the code.
I love this Dijkstra quote: If we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent": the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger.
As a formal Software Engineer, I now consider him to be possibly the most enlightened engineer that's ever existed in the field.
On one hand, his quotes have the ring of wisdom. On the other, the man was famously not an engineer and never brought a software project to fruition, to my knowledge.
These aren't necessarily contradictory, but you should keep it in mind. Knuth is much more of an engineer in the sense it's used on HN.
As to Dijkstra; enlightenment doesn't necessarily mean productive. Some of the least successful people I've ever met, can have absolutely sage like advice. Dijkstra was able to visualize things others could not, and explain novel solutions to those. On top of that he had a deep understanding of the life of a programmer and the complexities faced day in and day out.
You learn from mistakes. Unsuccessful = many mistakes?
This seems bad for code review, and disastrous with turnover. Whoops, now we need to find a scheme engineer whose eccentricities are roughly equivalent to this last guy.
There are also ways to achieve microservice-like architectures without requiring multiple code bases. See Actors (scala akka). Streams are an extension which give automatic load balancing. And if you need it to run over the network, akka-remote has you covered.
So then you have your coworker who wrote a wrapper around the code to sanitize all of the inputs and outputs and it's bigger than the actual code and once in a while it guesses wrong about ambiguous data.
Ryanair (the discount carrier) has a fleet of 354 boeing-737s.
This means they cannot fly some routes the bigger jets can, and other routes they burn more fuel than the smaller jets would.
BUUUT ... maintenance and logistics are drastically cheaper and efficient.
By analogy, I would argue that costs sunk either in training or hiring for specific skills will pay off in the long run.
IMO, microservices or SOA or whatever you want to call it, is primarily a method of organizing large teams so they don't get hamstrung by ops coordination. In that case you absolutely need it, but for smaller teams the overhead will usually far outweigh the benefit. I'm glad the space is being explored so that we get better tooling and techniques to lower that overhead, but when the dust settles I expect many small teams will discover that we collectively overestimated the better-understood pain of monolithic architectures and underestimated the less-understood pain of microservices.
Microservice architecture is very much orthogonal to loose coupling. I've worked on several microservice architectures with tight coupling. The microservice aspect actually exacerbated the pain caused by the tight coupling because it added the risk overhead of serialization/deserialization and network failures, which simply don't exist in a 'monolith' context.
Microservices when implemented are usually either a reflection of conway's law (ID team in building B has its own service; as does the CMS team in Europe), or fashion-driven development (one team working on 17 different services... which can be nasty).
Also: write less powerful code.
* Replace 'clever' metacode with straightforward imperative code where possible.
* Replace imperative code with declarative code where possible.
* Replace declarative code with configuration where possible.
The previous poster is basically saying "they screwed it up last time in 6 months, they'll screw it up again".
Microservices don't help. If they couldn't properly design their code without micro-services, throwing docker and micro-services in the mix won't make it magically better. It'll add massive amounts of complexity right off the bat. They'll still be bad coders and bad architects. They still won't know how to lay out their code. Having to split up their code will probably make a bad situation even worse.
They'll put the wrong methods on the wrong microservices, and share some 'key' code that shouldn't be shared as the method's on the wrong microservice, or worse still, cut and paste code and have it decay at different rates. They'll create new services that should actually be on an existing service and then gradually they code will duplicate, but with random subtle bugs.
The code debt won't disappear, it'll accelerate until they have a bunch of services they daren't touch and one of those services will become "the monolith" and eventually no-one's allowed to deploy to it or the whole thing will come falling down.
And all you're saying is "daylight".
What does "daylight" mean?
The very idea that micro-services will remain 'isolated' in a bad coding team is utterly deluded.
It's changing the culture around how things are developed and understood, and the parent poster is saying that exposing the team to daylight using something such as an architecture like microservices (or another "good practice") can help push the team toward a better understanding of what a good codebase and stable state should look like.
Stop thinking about the specific buzzword hyped up trendy term you're stuck on here; start thinking about the psychological factors that caused the situation in the first place, the environmental pressures that resulted in poor decisions, and the ways you can teach the team—by doing—how to create better applications.
Also:
> They'll still be bad coders
Most often that's not the root cause. Most often the root cause is poor management and poor leadership causing otherwise decent programmers with decent instincts to make decisions against the best interest of themselves and the company. The most common classic debt generation situation is trading off long-term quality for short-term speed and functionality. That is not "bad coders" at work; it's bad managers.
Attribution bias tells us that we tend to see behavior as more a product of individual traits than the system that produced it. The system is more responsible than we interpret in almost every case.
:(
The daylight word worked great for me, since the idea is to have everyone be able to see what's happening when they introduce changes.
If there's just one staging / integration environment where people are building things and patching over one another, and your local environment maybe has deviated a lot from either the staging, production, or even a clean local env, then there's all kinds of unexpected problems that might happen.
Getting more things, including your environment, under version control and requiring people to deploy things via files that live in version control rather than a smattering of deployment or env setup commands that not all of the team has visibility into, helps the entire development process go more smoothly.
I'm neutral on microservices, pro Docker.
What I'm properly alergic to is arcane setups where it's easier for everyone to share a server than it is for anybody to set up a copy of the system that is theirs and theirs alone. In a big enough shop, running all of the microservices you need on your dev box might be possible while running the whole system isn't, because you run out of memory before everything loads.
The bad coders often continue to be bad coders because nobody can 'prove' that it's their fault and so they keep dazzling the managers with bullshit and implying that you are the one with the problem, not them. Isolated, repeatable systems means you have to look at how crazy your architecture is instead of ignoring it, and regarding this conversation, there's a paper trail backing up your version of the story.
When people don't know which solution is better, they tend to back the side that has more trustworthy people on it, where trustworthy is "doesn't make messes, or helps clean them up when they do".
If you make it clear who's the problem and the project management still doesn't intervene, take your skills elsewhere. You're quitting with cause and many managers will value your commitment to sanity.
She isn't advocating using Docker for the target environment, she is advocating using Docker to set up a standard development environment that allows the developers to be productive[1] in 10 minutes rather than 10 hours.
I've been in the situation where the development environment is a poorly documented, massively multi-step steaming pile. The result is I spent a lot of time chasing down those responsible for the steaming pile (the "rockstar" in the department) asking them how to get the development environment working. Typically they pull the keyboard away from me, type a bunch, and then say "there, it works." Which it does. For a while.
Rinse and repeat for every developer. Regularly.
[1] Well, at least building the system rather than staring at mysterious very broken error messages.
You are trading an opaque irreproducible environment for another opaque environment that you reproduce by copying verbatim.
Technically, it's progress. But I can't imagine how people get satisfied by this.
In that case, wouldn't the Dockerfile be a relatively transparent way to see what's needed to reproduce an environment?
And they're written down where you can read them yourself. And maybe even version controlled!
That's if you can even find the original Dockerfiles - I recently ran into a situation where a container image was in the registry, but the original Dockerfile was completely lost.
Docker/containers in general move the complexity to a different plane, but they only move it - they don't eliminate it. Care needs to be taken to avoid situations just like the one above - or any of a number of other pitfalls - or you end up with an environment that's far more complex to understand than a VM housing a monolithic dev environment.
This would happen if they distributed Docker environments, too. Your setup process would be to download a Dockerfile, crank up everything you need, discover it doesn't work, call the Rockstar over, have him or her explain that there's a few manual steps that they haven't yet added to the codebase (or to the wiki), and then you rinse and repeat that process, instead.
Or, and I'm not sure if this is worse, a few people will fix it in slightly different ways, and start distributing those, leading to a weird ecosystem of competing Dockerfiles none of which are quite correct.
Nobody will volunteer to help you fix an obscure bug that only happens reliably in your version. Because it's too painful for them to shelve their local configuration and grab yours. And once people have that hesitation, 'all hands on deck' emergency fixes become window dressing, because only a few people can actually participate in tracking down and triaging the bug. Everyone else is sitting around waiting to be needed for the two minutes that they can contribute.
When you automate "setting up your environment", you are able to switch between several of them, create new ones for forks or extra tests, and roll back to debug production issues.
To this point, you might scaffold out a system to get something running and then incrementally / organically improve the system. I regard this as 'getting the system to the point where it can be dogfooded and/or criticized in a helpful way'.
At this point, despite what you might have running, it may be exceedingly clear that you should have used a different strategy. Maybe you like the overall results, but you need things to be implemented in a different way.
For example, I'm a C++ programmer. It's not always obvious at first that you need a specific set of virtual functions to make something work really nicely.
It's impossible to always know these sorts of things in advance.
Designing good design/abstractions like anything else takes practice. Armchair critiques won't cut it. If one's unwilling to fix bad design once it's identified, they're not practicing good design, they're practicing cobbling together something on a rickety foundation. How's that going to help next time one rights something from scratch?
I don't think I'd want to work with microservices and Docker containers designed by the same people who messed up another codebase.
Also, I've seen plenty of developers who were perfectly capable of screwing up codebases without being induced to do so by management (I've also seen lots of inept management, don't get me wrong. But I wrote the bad code, they didn't.)
A lot of engineers also seem to evaluate other engineers by their worst work. This is a toxic attitude. You can even take an engineer performing terribly in a good environment, and put them in a different good environment, and they can perform exceptionally. I've seen it happen more than once.
https://en.m.wikipedia.org/wiki/Fundamental_attribution_erro...
Note, the engineers have an ethical responsibility to not be bad. However bad is sometimes creating perfect code when something else is called for.
These things generally start with, "We need feature X for customer Y in Z days.". Engineers dive in, and then comments start popping up - usually variations on a theme: "This is a mess, but it works for now - we'll come back later and clean it up."
The unhealthy process that leads to that is a different discussion. But as engineers, our responsibility is to be aware that it's a reality, and work within those constraints.
Yes, there will be time/resource/budget limitations. No, we're not going to fix that overnight. But knowing it going in, we can take a closer look at some of those 'good enough for now' decisions, and do our best to not produce dog shit.
Sometimes dog shit can't be avoided, but if the pattern above is the normal way of doing business in a company, engineers do have the ability (and responsibility) to do the best they can within those constraints.
Often that means pausing and saying, "I know this is going to come back and bite us, what can we do now to make it better?"
They clean continously, not primarily to make the end-of-shift cleaning easier, but because it allows them to execute faster, better and more consistently (which in that environment is a necessary condition for executing at all).
Don't assume that all good code bases out there were written by teams with reasonable, well-defined and stable requirements, plenty of time and money and perfectly enlightened management. Very few projects are like that. Generally, I think you'll find they were written by good developers who kept their heads cool in the face of a range of challenges.
Absolutely, they will also largely not have had bosses that death marched them or changed requirements three times a day - as I said, management do obviously play a role.
It seems to me you should stop working for managers who are writing code.
I know it might sound glib but if thats the case then you're allowing yourself as a programmer to be set up to fail and be left holding the bag when things go to shit.
Either manage up in those situations or find a new job.
I've definitely cut corners for deadlines in my life but everyone always understands what debt we're accruing and when we'd get stuck paying that debt. If your status quo is "Do shitty work" you really need to find a new gig.
How often are the people who are currently maintaining the messed up codebase the ones who also originally designed it?
Likewise, microservices from monoliths can be done without containers. Just pick a chunk of the monolith that can be pried loose, and start with that. This can solve all sorts of problems.
What does work is assisting that team to slowly clean up the mess that they've built[1] by focusing on incremental improvements.
[1]: And we find that almost all messes were created for very understandable reasons.
Now it's a question of developer comfort and huge investment will be made to make sure everyone building the product are having the best experience doing so.
All the old code and knowledge is now debt because it can't come with us on this wonderful journey.
The thought should be "things would be easier going forward if we'd done X".
If it does the job and doesn't create issues then you just purchased it without taking on debt. This is easier if it is isolated/loosely coupled.
There's no point going back to improve it unless it saves you time/money in the future. If you make a better one to get the 'best experience' you are throwing away time and money for a slightly better mousetrap.
Yeah, no. Micro-services are neither recent, nor innovative.
Fun fact: a friend of my consults for large pensions funds in The Netherlands. One of the projects he was brought in on earlier this year to get an outside perspective on was this gigantic monument to micro-services that had been built up over a two year period by ~200 developers, at a cost of around 50 million euros.
After two months, every developer was fired, the project cancelled, and the entire 50 million euro cost written off.
But it had great micro-services, hundreds and hundreds of them!
Uh, how about we put all of the related stuff together so we have 8 services instead of 800? Could we maybe try that? How about something really crazy, why don't we give Conway's Law a try and arrange our code around organizational boundaries or vice versa?
We have a bunch of rules and guidelines that work toward that goal but if you don't know the destination you may never arrive.
Like or hate microservices, one thing they do is maintain a clear, hard boundary on the interfaces. There is no let me just reach into this class here, or call that there. It does work to keep people honest when the pressure is on.
Will it turn bad programmers into good ones? No, but I think the base assumption is that you're not necessarily dealing with bad programmers.