Live data from Hacker News

Microservices are a tax your startup probably can't afford

nexo.sh

221–230 of 272 posts

Re: Microservices are a tax your startup probably can't afford

#221

Earlier quoted context omitted.

Statically typed languages, when used correctly, save engineering time both as you extend your service and when thing go wrong as the compiler helps you check that the code you've written, to some degree, meets your specification of the problem domain. With a weak type system you can't specify much of the problem domain without increased labour but with a more expressive type system (and a team that understands how t…

The type system almost never catches a bug that proper testing would miss. And if the code has such nasty untested edge cases that you don't even notice a wrong type going somewhere, it'd probably behave wrongly even with the right types. Indeed "any" breaks type checking all around it, but it can be contained more easily in a helper func with a simple return type. Most common case is your helper does a SQL query, an…

The type system doesn't replace unit/snapshot/property/simulation tests as it's only job is specification. The type system is meant to be used in addition to testing to reduce the set of possible inputs to a smaller domain such that it's easier to reason about what is possible and what isn't. The same would be true even if you go as far as formal verification of programs, you always need to test even when you have powerful static types!

For example `foo :: Semigroup a, Traversable t => t a -> a` I already know that whatever is passed to this function can be traversed and have it's sum computed. It's impossible to pass something which doesn't satisfy both of those constraints as this is a specification given at the type level which is checked at compile-time. The things that cannot be captured as part of a type (bounded by the effort of specification) are then left to be captured by tests which only need to handle the subset of things which the above doesn't capture (e.g `Semigroup` specifies that you can compute the sum but it doesn't prevent `n + n = n` from being the implementation of `+`, that must be captured by property tests).

Another example, suppose you're working with time:

    tick :: Monad m => m Clock.Present

    zero    :: Clock.Duration
    seconds :: Uint -> Clock.Duration
    minutes :: Uint -> Clock.Duration
    hours   :: Uint -> Clock.Duration

    add :: Clock.Duration -> Clock.Present -> Clock.Future
    sub :: Clock.Duration -> Clock.Present -> Clock.Past

    is :: Clock.Duration -> Clock.Duration -> Bool

    until :: Clock.Future -> Clock.Present -> Clock.Duration
    since :: Clock.Past   -> Clock.Present -> Clock.Duration

    timestamp :: Clock.Present -> Clock.Past

    compare :: Clock.Present -> Clock.Foreign.Present -> Order

    data Order = Ahead Clock.Duration | Equal | Behind Clock.Duration
From the above you can tell what each function should do without looking at the implementation and you can probably write tests for each. Here the interface guides you to handle time in a safer way and tells a story `event = add (hours 5) present` where you cannot mix the wrong type of data ``until event `is` zero``. This is actual code that I've used in a production environment as it saves the team from shooting themselves in the foot with passing a `Clock.Duration` where a `Clock.Present` or `Clock.Future` should have been. Without a static type system you'd likely end up with a mistake mixing those integers up and not having enough test coverage to capture it as the space you must test is much larger than when you've constrained it to a smaller set within the bounds of the backing integer of the above.

In short, types are specifications, programs are proofs that the specification has a possible implementation, and tests ensure it behaves correctly for that the specification cannot constrain (or it would be too much effort to constrain it with types).

As for SQL, I'd rather say the issue is that the SQL schema is not encoded within your type system and thus when you perform a query the compiler cannot help you with inferring the type from the query. It's possible (in zig [1] at least) to derive the type of a prepared SQL query at compile-time so you write SQL as normal and zig checks that all types line up. It's not that types cannot do this, your tool just isn't expressive enough. F# [2] is capable of this through type providers where the database schema is imported making the type system aware of your SQL table layouts solving the "redundant specification" problem completely./

So with all of that, I assume (and do correct me if I'm wrong) that your view on what types can do is heavily influenced by typescript itself and you've yet to explore more expressive type systems (if so I do recommend trying Elm to see how you can work in an environment where `any` doesn't even exist). What you describe of types is not the way I experience them and it feels as if you're trying to fight against a tool that's there to help you.

[1]: https://rischmann.fr/blog/how-i-built-zig-sqlite [2]: https://github.com/fsprojects/SQLProvider

Re: Microservices are a tax your startup probably can't afford

#222

I see this a lot ("if you are a startup, just ship a monolith"). I think this is the wrong way to frame it. The advice should be "just do the scrappy thing". This distinction is important. Sometimes, creating a separate service is the scrappy thing to do, sometimes creating a monolith is. Sometimes not creating anything is the way to go. Let's consider a simple example: adding a queue poller. Let's say you need to ad…

> This distinction is important. Sometimes, creating a separate service is the scrappy thing to do, sometimes creating a monolith is. Sometimes not creating anything is the way to go.

I think this hits the nail on the head. People are trying to find the "one true way" for microservices vs monoliths. But it doesn't exist. It's context dependent.

It's like the DRY vs code duplication conversation. Trying to dictate that you will never duplicate code is a fool's errand, in the same way that duplicating code whenever something is slightly different is foolish.

Context is everything

Re: Microservices are a tax your startup probably can't afford

#223
post #149

Earlier quoted context omitted.

Are unit tests a shiny fad? Second time I've seen it mentioned in this thread. Is there some other type of testing I should be doing, or have I been doing it all wrong for the last two decades?

Integration testing? Less mocking, more bang for the buck.

Back in the day when I used to make "webapps" - our integration tests were an epic faff.

Often times using things like cucumber to describe a set of interactions, in the days before headless chrome it'd drive selenium which would literally open Firefox on your desktop and start navigating the site.

It did it's job but the feedback loop was slow.

Today I mainly write libraries, cli tools and the occasional small HTTP API. I still have unit and integration tests but they are all just standard pytest functions. I can run each type individually, my definition of an "integration" test is either something that talks to the outside world in some capacity, or something that is really slow (mostly running an ml model these days).

I much prefer today's approach, but admittedly I work on things with far fewer points of interaction which I think greatly simplifies the work.

Re: Microservices are a tax your startup probably can't afford

#224
post #2

> Microservices only pay off when you have real scaling bottlenecks, large teams, or independently evolving domains. Before that? You’re paying the price without getting the benefit: duplicated infra, fragile local setups, and slow iteration. For example, Segment eventually reversed their microservice split for this exact reason — too much cost, not enough value. Basically this. Microservices are a design pattern for…

> Microservices are a design pattern for organisations as opposed to technology

Very true in my experience. The main benefit is letting small groups of people work independently without stepping on each other’s toes. Although I’ve worked on a project where multiple teams owned micro services that were supposed to be standardized with each other, and it just lead to endless meetings and requirements churn since nobody was willing to work on the other teams service but everyone had an opinion on what the cross-team standard should be. Learning the diplomatic way to say “mind your own business” was more important than any technical skills for getting code merged.

Re: Microservices are a tax your startup probably can't afford

#226
post #177

Earlier quoted context omitted.

That's even more true for microservices, though, since I have yet to see a microservice architecture that automatically runs end to end tests before deploying. The post I was replying to said "your build will still break": that's what I was taking issue with. In this day and age there is no reason our trunk build should ever be broken.

> I have yet to see a microservice architecture that automatically runs end to end tests before deploying. One of the big tenets of independent services is that your APIs are contracts that don't change behaviour. As long as each individual service doesn't introduce breaking changes, the system as a whole should work as expected. If it doesn't this is indicative of either 1) a specific service lacking test coverage,…

> One of the big tenets of independent services is that your APIs are contracts that don't change behaviour

How is that any different from an API in a monolith not changing behavior?

Re: Microservices are a tax your startup probably can't afford

#227

I'll go against the grain and say that microservices have advantages for small dev teams embedded in non-tech orgs. 1. You get to minimize devops/security/admin work. Really a consequences of using serverless tooling, but you land on a something like a microservices architecture if you do. 2. You get can break out work temporally. This is the big one - when you're a small team supporting multiple products, you often…

Watch out for bit rot, though: it is very easy for a startup to come back to one of those microservices six months later and discover the dependencies are borked and it no longer even builds. Each repo you create is one more set of Dependabot alerts you need to keep on top of.

That’s just a devops failure tho. Nothing to do with microservices. And Renovatebot can batch updates weekly in single PRs if the toil is bad, which really cuts down and makes the load easy to bear.

Re: Microservices are a tax your startup probably can't afford

#228
post #190
post #114

Earlier quoted context omitted.

I saw one startup with about fifty engineers, and dozens of services. They had all of the problems that the post describes. Getting anything done was nearly impossible until you were in the system for at least six months and knew how to work around all the issues. Here’s the kicker: They only had a few hundred MAUs. Not hundreds of thousands. Hundreds of users. So all this complexity was for nothing. They burned thro…

I’m not sure where’s the downside. The engineers got paid, they managed to put “founder” on their cvs, and enjoyed the ride. Now they are more prepared for their next adventure. The only ones who lost money were the investors, but nobody cares about them.

Do you not care about the users either? They too will lose the service if the company shutters.

Re: Microservices are a tax your startup probably can't afford

#229
Doing microservices badly is a tax. But you have to ask if you’ve checked the boxes before doing them.

Do you have standardization and reuse of things like linting, formatting, ci/cd pipelines, version stability, deployment patterns, monitoring integrations, integration and end to end testing, etc.? If you’re doing those things bespoke per repo/deployment, or if you don’t have roles dedicated to the support and maintenance, you’re not going to have a good time with microservices.

Do you have actual issues of scale where API hot paths are dominating your runtime? Are they horizontally scalable or bottlenecked on downstream dependencies (databases)? You can’t solve scale issues by just spinning microservices willy nilly (e.g. by domain topic).

Is your development environment sophisticated enough to actually run a stack? Or do you have supporting clusters that allow for local binding of services? If not, you’re going to struggle with microservice local development, and pay for a slow QA in staging.

Does all that require supporting roles and expertise? Yeap. If you’re a 5 person startup you probably don’t have that. If you’re a 150 person startup, you might.

I’ve seen Java monoliths with 11M lines of code that represented 80% of the production cost to run and the gradual break out of targeted APIs to microservices halved that while the monolith still lived on. I’ve seen queued microservice architectures ripping through tens of millions of events/requests a minute with less than a thousand pods (across the services) and a fraction of the resources of the monolith.

Ultimately there’s no free lunch in software and you shouldn’t pursue any design without understanding the tradeoffs.

Re: Microservices are a tax your startup probably can't afford

#230
post #45

Earlier quoted context omitted.

Btw, do any good, modern CI tools support incremental rollout of multiple in-flight changes on monoliths? As in patch A is live, team B wants to rollout A+B and team C wants to rollout A+C. Ideally, A+B+C will eventually go live. Do cloud/paas providers deeply support this flow anymore? Every dashboard would need to compare across multiple live versions and I haven't tried that in a while.

I'd say this is a job for feature flags. That way you always have exactly one live version of the code, but still retain the ability to hide WIP from users until it's ready. If you're instead doing this with feature branches or something like that, then by definition you don't have CI. You have NI: Never Integration. Because, to an approximation, there's never any point in time where all of the code you're working on…

I don't dispute that good CI is facilitated by committing early and often but difference do feature flags really make? You still need to roll out features whether you tie that to versions or not.

If you're looking for performance regressions as you roll out feature flags, do you get any kind of support for that from built in dashboards? How do you get clean metrics if you only have one version and a bunch of teams turning knobs?

SOA/Microservices have a lot of headaches but you do get the option for very tightly scoped deployments or feature rollouts.

Post reply on HN