As a PHP developer, I should probably rejoice when someone's saying "we do microservices in PHP". I don't. I'm fine with PHP and I like microservices but the combination is just bad. PHP is a tool for solving a narrow range of tasks (i.e. making websites) and if you're trying to do something out of this range (i.e. making microservices) — you're gonna have a bad time.
In this post, I tried to collect all microservice-related PHP problems I saw over the years.
Docker containers
Let's start with simple things. The rise of microservices follows the rise of containers. And it works great together: you make a small web app, wrap it into a container, throw it to cloud and 🎉 you have a microservice.
It works probably with every language except PHP. On one hand PHP is bragging about being the first serverless, on the other hand, it requires an external HTTP server to work. Even for simplest "hello world" you need to create two containers (nginx and php-fpm) and configure them to work together. Hello, nginx configs and docker networks. Goodbye illusion about services being "micro".
Global state
Once I read an article about implementing search suggestion microservice. It was created for a big ecommerce website and the biggest challenge authors had is to structure search index right way to make it require less memory. After that, they just put the whole index into a variable and then they made a service that serves suggestions from a variable. My first reaction was "wow this is a perfect microservice... and you can never do that in PHP". Because you can not have a global variable that is preserved between requests. In PHP we use a database for that. Or a memcached. You can also read that as "In PHP we use a separate service with increased latency and risk of failure for that".
Messaging
No microservice is an island. They start shining when there's a lot of them and they talk to each other. How do they do that? The default PHP solution is REST-like servers and Guzzle clients. And it's problematic because:
- With REST you have to invent an application-layer protocol again every time and somehow mix it with transport-layer protocol (that's offtopic though).
- I/O operations in PHP are blocking (ReactPHP is trying to solve that but it's not popular enough). This means if service A calls service B and service B calls service C - thread in service A is doing nothing but waiting for service C to respond. To avoid it you can call several services in parallel and wait for them all to finish but that doesn't solve everything.
- If service C is down - it could bring down services B and A also causing cascading failure.
There's a technique called Circuit Breaker designed to mitigate cascading failures. If service C is failing for some time - you start calling it and immediately fail all requests for a while. With this you achieve two goals: service B is failing fast without waiting for the request to timeout and you're giving service C some chance to restore. Circuit Breaker needs a global state that persists between requests. You don't have that in PHP.
One more thing. Have you heard about grpc? It's a protocol/framework created by Google to simplify and optimize RPC-calls between microservices. You can't have a grpc server in php.
But you still have async messaging. Use it when you can.
Concurrency control
Sometimes you need to handle concurrent requests consistently. For example, if there is an event with only one ticket left and you get two requests to buy that ticket at the same time — you want one of those requests to succeed and the second one to fail. What you definitely not want is to sell more tickets than you have.
In PHP you may want to use optimistic locking for that but it comes with its own problems. It leads to excessive computations and if you get two requests at the same time - one of them will fail even if there are plenty of tickets left.
A better way to do that is actor model. You spawn an actor instance for each event and all requests regarding this event come to the message queue of this actor instance no matter which server got the request from outside. As a result, all "buy ticket" requests are processed sequentially in a single place. Isn't it cool? Now you don't have to worry about concurrency. All you need to worry about is how to maintain an actor cluster :trollface:.
Of course, you can't have actors in PHP. I found two github projects about it. The first one is a PHP extension written in C. It still has things like "stabilize things" and "implement remote actors" in the roadmap. The second one is an April Fools' joke.
Summary
So what can you do? You can create a bunch of REST servers, connect them with Guzzle and call that house of cards a microservice-oriented architecture. Or you can connect everything with message queues and struggle with PHP trying to process more that one message at a time. Or you can use memcached, reactphp, grpc-php and other crutches to do things PHP doesn't let you.
Or you can use any other language and leave PHP the task of making small websites. I heard server-side rendering is in fashion again.
Do you know other problems? Do you think someone on the Internet is wrong and that's me? Let me know in the comments.