sudo nano /etc/nginx/sites-available/booking-api
For smaller applications, this level of infrastructure is difficult to justify. The initial setup, ongoing monitoring, and team knowledge required to run microservices reliably often outweigh any performance or scalability benefit you might gain.
What Monolithic Architecture Looks Like in Practice
A monolithic PHP application runs as a single deployable unit. All business logic, database operations, routing, and presentation layer live together in one codebase. When you update a booking function, you deploy the entire application. This simplicity is a genuine advantage for small to medium-sized projects.
Most booking systems, business websites, and internal tools I work with do not need distributed services. A well-structured monolith with clear separation of concerns within the codebase handles the workload without the operational burden that comes with microservices. The key phrase there is "well-structured." A messy monolith is not an argument for microservices; it is an argument for better code organisation.
Where Microservices Can Make Sense
There are scenarios where the microservices model provides real value. These typically involve multiple independent teams, separate product domains that evolve at different rates, or systems that need to scale specific components independently under high load.
- High-traffic applications with variable load: If your booking system experiences sudden spikes in traffic while your reporting module sits idle, scaling only the booking service can reduce infrastructure costs.
- Multiple technology requirements: When different parts of your system genuinely benefit from different languages or frameworks, microservices allow each team to work with the right tool for their domain.
- Independent deployment requirements: If your payment processing, notification system, and booking logic all have separate release cycles, microservices reduce the risk of deploying them together.
- Clear domain boundaries: When your business domains are genuinely separate and have well-defined APIs between them, the service boundary makes logical sense.
For most small businesses and solo operators in the UK, these conditions do not apply. Your booking system and your email notifications and your reporting dashboard are all closely related. Splitting them into separate services adds complexity without proportional benefit.
Communication Overhead Between Services
One of the least discussed costs of microservices is network communication. In a monolith, a function call between two modules is a direct memory operation. In a microservices setup, that same interaction becomes a network request over HTTP, a message queue, or gRPC.
Network requests can fail. They introduce latency. They require retry logic, timeout handling, and circuit breakers. Each of these is additional code to write, test, and maintain. For a booking system where a customer submits a form and expects a confirmation, this network overhead can measurably degrade the user experience if not handled carefully.
# Example: simple booking confirmation in a monolith
function confirmBooking($bookingId) {
$booking = Booking::find($bookingId);
$booking->status = 'confirmed';
$booking->save();
sendConfirmationEmail($booking->customer_email, $booking);
updateCalendar($booking);
notifyStaff($booking);
}
// In a microservices setup, that same workflow becomes:
// POST /api/bookings/{id}/confirm
// -> triggers email-service via message queue
// -> triggers calendar-service via message queue
// -> triggers notification-service via message queue
Each of those message queue calls introduces a potential failure point. You now need to handle partial failures where some services receive the message and others do not.
Data Consistency Across Services
Relational databases work brilliantly when all your data lives in one place. Transactions keep things consistent. Queries are fast and predictable. Microservices complicate this because each service typically owns its own database.
When a booking is confirmed, you may need to update the bookings database, the payments database, and the inventory database simultaneously. With a monolith, a database transaction handles this cleanly. With microservices, you need either distributed transactions, which are complex and slow, or a saga pattern, which requires careful choreography of multiple service interactions.
For most booking systems, the complexity of maintaining data consistency across service boundaries is not worth the architectural flexibility it provides. A single well-indexed database with proper transaction management handles thousands of concurrent bookings without issue.
When the Operational Burden Becomes Unjustifiable
Running microservices in production requires infrastructure that a monolith does not need. You need a service mesh or API gateway to route traffic between services. You need container orchestration, typically Kubernetes or a managed equivalent. You need distributed tracing to understand why a request failed and which service caused it. You need centralised logging across all services. You need a strategy for secret management across multiple deployments.
Each of these is a separate system to configure, monitor, update, and troubleshoot when something goes wrong. For a solo developer or small team, this operational surface area can become unmanageable. The time you spend maintaining the infrastructure is time you are not spending on the actual business logic of your application.
What Most PHP Projects Actually Need
For the majority of PHP projects I encounter, a modular monolith is the right starting point. This means writing your code in clearly separated modules within a single application, with defined interfaces between them, but deploying everything together. You get the code organisation benefits of microservices without the operational complexity.
If you later find a genuine performance bottleneck in one specific module, you can extract that module as a separate service at that point, based on real measurements rather than theoretical scalability. Premature optimisation, including premature microservices adoption, tends to create more problems than it solves.
A booking system built with clear separation between the booking domain, payment processing, and notification logic serves most small businesses well. If the business grows to the point where separate teams are maintaining each domain, or where independent scaling becomes necessary, the extraction becomes a planned migration rather than an architectural gamble.
How to Evaluate Your Architecture Decision
Before deciding on microservices for a PHP project, ask yourself a few practical questions. How many developers will be working on this system simultaneously? If you have three developers stepping on each other's changes in a single codebase, microservices will make that worse, not better, because you now have three developers managing three separate codebases with complex interactions between them.
What is your current pain point? If it is slow queries, slow deployments, or poor code organisation, microservices will not fix those. Those are problems with the monolith itself, and better code, better indexing, or better deployment pipelines solve them directly.
Do you have the monitoring and incident response infrastructure to support distributed systems? When a booking fails in a monolith, you can trace the issue in one codebase. When a booking fails across five microservices, you need distributed tracing, log aggregation, and the ability to correlate events across service boundaries.
When to Start Considering Services
There is a practical threshold where microservices begin to make sense, even in PHP environments. If you are running multiple independent products that share some data but evolve separately, separate services reduce coordination overhead. If you have a PHP application that is CPU-bound on specific tasks, offloading those tasks to a purpose-built service written in a more performant language can improve overall system throughput.
For example, a custom booking system that handles peak loads during flash sales may benefit from extracting the real-time inventory check to a lightweight Go or Rust service while keeping the main booking flow in PHP. This is a targeted architectural decision based on a specific performance requirement, not a blanket adoption of microservices.
The decision should always be driven by concrete requirements, measured performance data, and a clear understanding of the additional operational cost you are taking on.
Related practical reading
These related guides can help you connect this topic with the wider website, server, security, and support decisions around it.
- GraphQL in PHP vs REST: When GraphQL Is the Better Choice - useful background for related development decisions
- PHP 8.4: Property Hooks and Asymmetric Visibility - useful background for related development decisions