Hacker News new | past | comments | ask | show | jobs | submit login

Nothing prevents you from having multiple microservices sharing the same codebase.

That said, the "it's just like the web" model doesn't sound fantastic to me. It sounds like your app now depends on contracts which are only enforced by good practices, not by something strongly typed you can check at compile time, unless you use something like protocol buffers to generate the boilerplate.




This is where the test-driven world, which in my experience is strongest on the dynamic language side of programming, has come back around full circle.

In microservices, everything is dynamically typed.

There is no single binary produced by a single compiler performing whole-program checks of consistency. Even tools like protobufs don't help when code bases drift, or someone introduces a foreign tool, or someone upgrades versions and introduces a subtle mismatch, or some doesn't know you call their service and shuts it down ...

Turns out that driving from tests, and starting those tests from the outermost consumer, is a fairly well-proved way of coping with such conditions.


> There is no single binary produced by a single compiler performing whole-program checks of consistency. Even tools like protobufs don't help when code bases drift, or someone introduces a foreign tool, or someone upgrades versions and introduces a subtle mismatch, or some doesn't know you call their service and shuts it down ...

Static typing is not a panacea, but large codebase plus dynamic typing everywhere sounds like a recipe for disaster. No matter the amount of testing.

> Turns out that driving from tests, and starting those tests from the outermost consumer, is a fairly well-proved way of coping with such conditions.

You need tests no matter what. However, static typing means a much greater confidence in your codebase.


As soon as you distribute your system, you have dynamic typing, whether you like it or not.

At runtime you are inspecting incoming messages and then routing them to code. It doesn't matter what language the code is written in, it will need to route and validate the messages at runtime.

The type system cannot provide compile-time assurances of behaviour, because it cannot create a single consistent binary which enforces the guarantees.

Your only remaining tool is to drive code from tests and only from tests.


> As soon as you distribute your system, you have dynamic typing, whether you like it or not.

You have serialization/deserialization issues. You can still type your messages.

> At runtime you are inspecting incoming messages and then routing them to code. It doesn't matter what language the code is written in, it will need to route and validate the messages at runtime.

Of course.

> The type system cannot provide compile-time assurances of behaviour, because it cannot create a single consistent binary which enforces the guarantees.

If you make the assumption that you deploy up-to-date binaries, then knowing at compile time that your producer and consumer use the same data structure for the messages they exchange would give me much better confidence than "it looks like the API conforms to what's written on the wiki".


> You can still type your messages.

You can hope that they respect the type. For a robust distributed system, you will have to check everything at runtime.

> If you make the assumption that you deploy up-to-date binaries, then knowing at compile time that your producer and consumer use the same data structure for the messages they exchange would give me much better confidence than "it looks like the API conforms to what's written on the wiki".

My reading is that we agree that running code is the only source of truth, we disagree on what guarantees distribution deprives us of.


If you cannot ensure that your producer receives messages following a certain schema, even though you enforce it statically in your codebase, you also cannot ensure that your running code passes your tests.


Which is why I start from integration testing of the whole system, with frenemy tests for any foreign services that I must rely on.

You're right that tests don't make Byzantine failures go away. But neither do static types. My point that distribution turns all systems into analogies for dynamic language programming remains, and so the emphasis on tool support changes along with it.


This reminded me of the AngularJS team deciding to go with (optional) runtime type checking over compile-time checking (which is what TypeScript has done to JavaScript). Their reasoning was that you can use runtime checking for REST responses, which can be argued to somewhat reduce the need for writing tests.


Most systems I've seen these days don't have any compile-time type checking, since they're all written in Ruby, Python, or Node.js.

In the normal case of development, you tend to have a broken-out system. For a game for example:

    + Game code
    --+ User authentication classes/functionality (which accesses DB)
    --+ Messaging classes/functionality (which accesses DB)
    --+ User metrics classes/functionality (which accesses DB)
In the new design you'd have this:

    + Game code
    --+ User authentication classes/functionality (which accesses REST service)
    --+ Messaging classes/functionality (which accesses REST service)
    --+ User metrics classes/functionality (which accesses REST service)
In other words, in a clean design, your Game code is accessing a library which provides user Authentication functionality, one which provides Messaging functionality, and one which provides Metrics functionality.

In this new design, you have exactly the same thing - a library which abstracts the details of communicating with the service, encoding data, etc. A person making changes to those libraries, which other services use, is responsible for either not making backwards-incompatible changes, or, when that isn't possible, working with other teams to ensure a clean upgrade path (or doing it themselves, if your lines are sufficiently blurred).


> In this new design, you have exactly the same thing - a library which abstracts the details of communicating with the service, encoding data, etc. A person making changes to those libraries, which other services use, is responsible for either not making backwards-incompatible changes, or, when that isn't possible, working with other teams to ensure a clean upgrade path (or doing it themselves, if your lines are sufficiently blurred).

The new design trades a "modular but monolithic" design for complexity and brittleness, IMHO. The ability to spin up new instances of a given service on demand is interesting, but it sure sounds like reinventing Erlang without Erlang's tooling.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: