I totally agree. Mostly we are doing microservices the wrong way. We are not drawing the boundaries correctly, they are too small, they have too many interdependencies, they don't really encapsulate the data, and you end up with many interdependencies. There is not enough guidance about sizing them. We are just building distributed monoliths. Which is great for cloud companies because they get to sell many boxes.
Micro-services are just connected things which work together to accomplish something. But where are those connections described? In some tables somewhere. Maybe.
Whereas if you write a single monolithic program its connections are described in code, preferably type-checked by a compiler. I think that gives you at least theoretically a better chance of understanding what are the things that connect, and how they connect.
So if there was a good programming language for describing micro-services then that would probably resolve many management difficulties, and then the question would be simply do we get performance benefits from running on multiple processors.
Literally this. Reading the article I kept thinking to myself "which is why Erlang/Elixir is great because it doesn't make you choose up front". It's wild that with how popular Elixir has gotten that it still isn't seen as a serious contender for many companies
I write Elixir professionally, Erlang/Elixir and BEAM is no one-stop solution to these problems either. It has tools to help you, but you can very easily end up in the same boat.
I have never tried Erlang, but I have read that it doesn't have static type checking. How does it guarantee that different services are following protocols?
Erlang is dynamically typed in part, I believe, to allow hot-swapping code in a running system. It relies on pattern matching to ensure code contracts / ie your types are more like guarantees that a pattern holds true, even if some of the specifics of that protocol have changed. Thus, with zero downtime, you can make updates to the system, while still knowing that your assertions about received data matching your protocol remains true. Any adapters can act like both type update and schema migration simultaneously, as in the case where you wish to support multiple versions of an API simultaneously.
The runtime system has built-in support for concurrency, distribution and fault tolerance. Because of the design goals for Erlang and it's runtime, you get services that can all run on one system or be distributed across a network, but the code that you actually write is relatively simple; the entire distributed system acts as a fault-tolerant VM that has functional guarantees.
If your startup node fails, then other nodes are elected. If a node crashes while in the middle of a method, another node will execute the method instead.
The runtime itself has some analogies to functional coding styles. It runs on a register machine rather than a stack machine. The call and return sequence is replaced by direct jumps to the implementation of the next instruction.