Table of Contents
Architecture Overview
Microservices Split Principles
- Split by Business Domain - Based on Bounded Context
- Single Responsibility - Each service handles only one business domain
- Independent Deployment - Services can be developed, tested, and deployed independently
- Data Independence - Each service has its own database
Service Architecture Diagram
┌─────────────────────────────────────────────────────────┐
│ API Gateway │
│ (Webman Gateway) │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ User Service │ │Order Service │ │Payment Service│
│ (Webman) │ │ (Webman) │ │ (Webman) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ MySQL │ │ MySQL │ │ MySQL │
│ (Users DB) │ │ (Orders DB) │ │(Payments DB) │
└──────────────┘ └──────────────┘ └──────────────┘
┌─────────────────────────────┐
│ Message Queue (Redis) │
│ Event Bus / RPC │
└─────────────────────────────┘Service Split
1. User Service
Responsibilities:
- User registration/login
- User information management
- Authentication and authorization
- User permission management
Port: 8001
2. Order Service
Responsibilities:
- Order creation
- Order status management
- Order query
- Inventory deduction
Port: 8002
3. Payment Service
Responsibilities:
- Payment processing
- Refund processing
- Payment status query
- Payment gateway integration
Port: 8003
4. API Gateway
Responsibilities:
- Route forwarding
- Authentication
- Rate limiting and circuit breaking
- Logging and monitoring
Port: 8000
Directory Structure
Overall Project Structure
microservices/
├─ gateway/ # API Gateway
│ ├─ app/
│ │ ├─ controller/
│ │ │ └─ GatewayController.php
│ │ ├─ middleware/
│ │ │ ├─ AuthMiddleware.php
│ │ │ ├─ RateLimitMiddleware.php
│ │ │ └─ CircuitBreakerMiddleware.php
│ │ └─ service/
│ │ ├─ RouteService.php
│ │ └─ LoadBalancerService.php
│ ├─ config/
│ └─ start.php
│
├─ user-service/ # User Service
│ ├─ app/
│ │ ├─ controller/
│ │ │ └─ api/
│ │ │ └─ v1/
│ │ │ ├─ UserController.php
│ │ │ └─ AuthController.php
│ │ ├─ service/
│ │ │ ├─ user/
│ │ │ │ ├─ CreateUserService.php
│ │ │ │ └─ UpdateUserService.php
│ │ │ └─ auth/
│ │ │ ├─ LoginService.php
│ │ │ └─ RegisterService.php
│ │ ├─ domain/
│ │ │ └─ user/
│ │ │ ├─ entity/
│ │ │ │ └─ User.php
│ │ │ └─ vo/ # Value Objects
│ │ │ ├─ Email.php
│ │ │ └─ Password.php
│ │ ├─ contract/
│ │ │ └─ repository/
│ │ │ └─ UserRepositoryInterface.php
│ │ └─ infrastructure/
│ │ ├─ repository/
│ │ │ └─ eloquent/
│ │ │ └─ EloquentUserRepository.php
│ │ └─ event/
│ │ └─ UserEventPublisher.php
│ ├─ config/
│ │ └─ database.php
│ └─ start.php
│
├─ order-service/ # Order Service
│ ├─ app/
│ │ ├─ controller/
│ │ │ └─ api/
│ │ │ └─ v1/
│ │ │ └─ OrderController.php
│ │ ├─ service/
│ │ │ └─ order/
│ │ │ ├─ CreateOrderService.php
│ │ │ └─ CancelOrderService.php
│ │ ├─ domain/
│ │ │ └─ order/
│ │ │ ├─ entity/
│ │ │ │ └─ Order.php
│ │ │ ├─ enum/ # Enums
│ │ │ │ └─ OrderStatus.php
│ │ │ └─ vo/ # Value Objects
│ │ │ └─ Money.php
│ │ ├─ contract/
│ │ │ ├─ repository/
│ │ │ │ └─ OrderRepositoryInterface.php
│ │ │ └─ client/
│ │ │ ├─ UserServiceClientInterface.php
│ │ │ └─ PaymentServiceClientInterface.php
│ │ └─ infrastructure/
│ │ ├─ repository/
│ │ │ └─ eloquent/
│ │ │ └─ EloquentOrderRepository.php
│ │ ├─ client/
│ │ │ ├─ UserServiceClient.php # Call user service
│ │ │ └─ PaymentServiceClient.php # Call payment service
│ │ └─ event/
│ │ └─ OrderEventPublisher.php
│ ├─ config/
│ │ ├─ database.php
│ │ └─ services.php # Service address config
│ └─ start.php
│
├─ payment-service/ # Payment Service
│ ├─ app/
│ │ ├─ controller/
│ │ │ └─ api/
│ │ │ └─ v1/
│ │ │ └─ PaymentController.php
│ │ ├─ service/
│ │ │ └─ payment/
│ │ │ ├─ CreatePaymentService.php
│ │ │ └─ ProcessPaymentService.php
│ │ ├─ domain/
│ │ │ └─ payment/
│ │ │ ├─ entity/
│ │ │ │ └─ Payment.php
│ │ │ ├─ enum/ # Enums
│ │ │ │ └─ PaymentStatus.php
│ │ │ └─ vo/ # Value Objects
│ │ │ └─ Money.php
│ │ ├─ contract/
│ │ │ ├─ repository/
│ │ │ │ └─ PaymentRepositoryInterface.php
│ │ │ └─ gateway/
│ │ │ └─ PaymentGatewayInterface.php
│ │ └─ infrastructure/
│ │ ├─ repository/
│ │ │ └─ eloquent/
│ │ │ └─ EloquentPaymentRepository.php
│ │ ├─ gateway/
│ │ │ └─ stripe/
│ │ │ └─ StripePaymentGateway.php
│ │ └─ event/
│ │ └─ PaymentEventPublisher.php
│ ├─ config/
│ │ └─ database.php
│ └─ start.php
│
└─ shared/ # Shared Library
├─ src/
│ ├─ event/
│ │ ├─ EventBus.php
│ │ └─ EventSubscriber.php
│ ├─ http/
│ │ └─ ServiceClient.php # HTTP client base class
│ └─ vo/
│ └─ Money.php # Shared value object
└─ composer.jsonKey Code Examples
1. Inter-Service Communication - HTTP Client Base Class
php
<?php
declare(strict_types=1);
namespace shared\http;
use Saloon\Http\Connector;
use Saloon\Traits\Plugins\AcceptsJson;
/**
* Service Client Base Class
*/
abstract class ServiceClient extends Connector
{
use AcceptsJson;
abstract protected function getServiceName(): string;
public function resolveBaseUrl(): string
{
// Read service address from config
$services = config('services');
$serviceName = $this->getServiceName();
if (!isset($services[$serviceName])) {
throw new \RuntimeException("Service {$serviceName} not configured");
}
return $services[$serviceName]['url'];
}
protected function defaultHeaders(): array
{
return [
'X-Service-Name' => config('app.name'),
'X-Request-Id' => $this->generateRequestId(),
];
}
private function generateRequestId(): string
{
return uniqid('req_', true);
}
}2. User Service Client
php
<?php
declare(strict_types=1);
namespace app\infrastructure\client;
use shared\http\ServiceClient;
/**
* User Service Client
*/
final class UserServiceClient extends ServiceClient
{
protected function getServiceName(): string
{
return 'user-service';
}
public function getUserById(int $userId): ?array
{
$response = $this->send(
new \app\infrastructure\client\requests\GetUserRequest($userId)
);
if (!$response->successful()) {
return null;
}
return $response->json('data');
}
public function verifyUser(int $userId): bool
{
$response = $this->send(
new \app\infrastructure\client\requests\VerifyUserRequest($userId)
);
return $response->successful();
}
}3. Order Service - Create Order
php
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\contract\client\UserServiceClientInterface;
use app\contract\client\PaymentServiceClientInterface;
use app\domain\order\entity\Order;
use support\Db;
/**
* Create Order Service
*/
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly UserServiceClientInterface $userServiceClient,
private readonly PaymentServiceClientInterface $paymentServiceClient
) {
}
public function handle(
int $userId,
array $items,
array $shippingAddress
): Order {
return Db::transaction(function () use ($userId, $items, $shippingAddress) {
// 1. Call user service to verify user
if (!$this->userServiceClient->verifyUser($userId)) {
throw new \RuntimeException('User not found or inactive');
}
// 2. Create order entity
$order = Order::create(
userId: $userId,
items: $items,
shippingAddress: $shippingAddress
);
// 3. Calculate total amount
$order->calculateTotal();
// 4. Persist order
$this->orderRepository->save($order);
// 5. Call payment service to create payment
$paymentResult = $this->paymentServiceClient->createPayment(
orderId: $order->id(),
amount: $order->totalAmount()->toDollars(),
currency: 'USD'
);
// 6. Update order payment info
$order->setPaymentId($paymentResult['payment_id']);
$this->orderRepository->save($order);
return $order;
});
}
}4. Event Bus - Publish/Subscribe Pattern
php
<?php
declare(strict_types=1);
namespace shared\event;
use support\Redis;
/**
* Event Bus
*/
final class EventBus
{
private const CHANNEL_PREFIX = 'events:';
public function publish(string $eventName, array $payload): void
{
$channel = self::CHANNEL_PREFIX . $eventName;
$message = json_encode([
'event' => $eventName,
'payload' => $payload,
'timestamp' => time(),
'service' => config('app.name'),
]);
Redis::publish($channel, $message);
}
public function subscribe(string $eventName, callable $handler): void
{
$channel = self::CHANNEL_PREFIX . $eventName;
Redis::subscribe([$channel], function ($redis, $channel, $message) use ($handler) {
$data = json_decode($message, true);
$handler($data['payload']);
});
}
}5. API Gateway - Route Forwarding
php
<?php
declare(strict_types=1);
namespace app\controller;
use support\Request;
use support\Response;
use GuzzleHttp\Client;
/**
* Gateway Controller
*/
final class GatewayController
{
private array $serviceMap = [
'/api/users' => 'user-service',
'/api/orders' => 'order-service',
'/api/payments' => 'payment-service',
];
public function __construct(
private readonly Client $httpClient
) {
}
public function forward(Request $request): Response
{
// 1. Resolve target service
$service = $this->resolveService($request->path());
if ($service === null) {
return json(['error' => 'Service not found'], 404);
}
// 2. Get service address
$serviceUrl = config("services.{$service}.url");
// 3. Forward request
try {
$response = $this->httpClient->request(
$request->method(),
$serviceUrl . $request->path(),
[
'headers' => $this->forwardHeaders($request),
'json' => $request->all(),
'timeout' => 30,
]
);
return response(
$response->getBody()->getContents(),
$response->getStatusCode()
);
} catch (\Exception $e) {
logger()->error('Gateway forward failed', [
'service' => $service,
'path' => $request->path(),
'error' => $e->getMessage(),
]);
return json(['error' => 'Service unavailable'], 503);
}
}
private function resolveService(string $path): ?string
{
foreach ($this->serviceMap as $prefix => $service) {
if (str_starts_with($path, $prefix)) {
return $service;
}
}
return null;
}
private function forwardHeaders(Request $request): array
{
return [
'Authorization' => $request->header('Authorization'),
'X-Request-Id' => $request->header('X-Request-Id', uniqid('req_')),
'X-Forwarded-For' => $request->getRealIp(),
];
}
}6. Circuit Breaker Middleware
php
<?php
declare(strict_types=1);
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
use support\Redis;
/**
* Circuit Breaker Middleware
*/
final class CircuitBreakerMiddleware implements MiddlewareInterface
{
private const FAILURE_THRESHOLD = 5;
private const TIMEOUT_SECONDS = 60;
public function process(Request $request, callable $handler): Response
{
$service = $this->getServiceName($request);
// Check circuit breaker status
if ($this->isCircuitOpen($service)) {
return json([
'error' => 'Service temporarily unavailable',
'service' => $service,
], 503);
}
try {
$response = $handler($request);
// Request successful, reset failure count
if ($response->getStatusCode() < 500) {
$this->recordSuccess($service);
} else {
$this->recordFailure($service);
}
return $response;
} catch (\Exception $e) {
// Request failed, record failure
$this->recordFailure($service);
throw $e;
}
}
private function isCircuitOpen(string $service): bool
{
$key = "circuit_breaker:{$service}:failures";
$failures = (int) Redis::get($key);
return $failures >= self::FAILURE_THRESHOLD;
}
private function recordFailure(string $service): void
{
$key = "circuit_breaker:{$service}:failures";
Redis::incr($key);
Redis::expire($key, self::TIMEOUT_SECONDS);
}
private function recordSuccess(string $service): void
{
$key = "circuit_breaker:{$service}:failures";
Redis::del($key);
}
private function getServiceName(Request $request): string
{
$path = $request->path();
if (str_starts_with($path, '/api/users')) {
return 'user-service';
}
if (str_starts_with($path, '/api/orders')) {
return 'order-service';
}
if (str_starts_with($path, '/api/payments')) {
return 'payment-service';
}
return 'unknown';
}
}Inter-Service Communication
1. Synchronous Communication - HTTP/REST
Use Cases: Operations that require immediate results
Advantages:
- Simple and intuitive
- Easy to debug
- Real-time response
Disadvantages:
- Service coupling
- Performance bottleneck
- Cascading failures
2. Asynchronous Communication - Message Queue
Use Cases: Operations that don't require immediate response
Implementation: Redis Pub/Sub or RabbitMQ
Advantages:
- Service decoupling
- Peak shaving
- High availability
Disadvantages:
- Higher complexity
- Eventual consistency
- Difficult to debug
3. Event-Driven Architecture
Core Concepts:
- Event Publishing: Services publish domain events
- Event Subscription: Other services subscribe to events of interest
- Event Processing: Asynchronous event handling
Example Flow:
1. Order service creates order -> Publish "order.created" event
2. Payment service subscribes -> Create payment record
3. Inventory service subscribes -> Deduct inventory
4. Notification service subscribes -> Send notificationBest Practices
Service Split
- Split by Business Domain: Based on DDD bounded contexts
- Avoid Over-splitting: Don't microservice for the sake of microservices
- Data Independence: Each service has independent database
- Interface Stability: Keep APIs backward compatible
Service Governance
- Service Registration & Discovery: Use Consul or Etcd
- Load Balancing: Use Nginx or service mesh
- Circuit Breaking & Degradation: Prevent cascading failures
- Rate Limiting: Protect services from being overwhelmed
Data Consistency
- Eventual Consistency: Use event-driven to ensure eventual consistency
- Distributed Transactions: Use Saga pattern
- Compensation Mechanism: Execute compensation on failure
- Idempotency: Ensure consistent results on repeated calls
Monitoring and Logging
- Distributed Tracing: Use Request ID to trace request chain
- Centralized Logging: Use ELK to collect logs
- Performance Monitoring: Monitor service response time and error rate
- Alerting: Timely detection and handling of issues