MATIH Platform is in active MVP development. Documentation reflects current implementation status.
2. Architecture
Shared Patterns

Shared Patterns Across Control Plane Services

Production - commons-java library enforces consistent patterns

All 10 Control Plane services implement the same architectural patterns, enforced through the commons-java library. This consistency reduces cognitive overhead when switching between services and ensures uniform behavior in security, error handling, observability, and multi-tenancy.


2.3.F.1Security Filter Chain

Every request passes through an ordered filter chain before reaching any controller:

Request arrives
  |
  v
SecurityFilter (HIGHEST_PRECEDENCE + 10)
  - Validate request headers for injection attacks
  - Check X-Tenant-ID, X-User-ID, X-Request-ID format
  - Reject path traversal, null bytes, oversized payloads
  |
  v
JwtAuthenticationFilter (HIGHEST_PRECEDENCE + 20)
  - Extract Bearer token from Authorization header
  - Validate JWT signature, expiry, issuer, type
  - Check blacklist (Redis: blacklist:{jti})
  - Set Spring Security context
  |
  v
TenantContextFilter (HIGHEST_PRECEDENCE + 30)
  - Read X-Tenant-ID header (set by gateway)
  - Call TenantContextHolder.setTenantId()
  - ALWAYS clear context in finally block
  |
  v
RbacFilter (HIGHEST_PRECEDENCE + 40)
  - Read @RequirePermission from target controller method
  - Check user roles against required permissions
  - Return 403 Forbidden if insufficient
  |
  v
Controller --> Service --> Repository

The SecurityFilter is critical for defense-in-depth. Even though the Kong gateway performs input validation, the backend SecurityFilter provides a second layer that catches any requests that bypass the gateway (e.g., direct pod-to-pod communication within the cluster).


2.3.F.2API Response Envelope

All Control Plane APIs return responses in a consistent envelope format:

Success Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "ACME Corporation",
    "tier": "enterprise",
    "status": "active"
  },
  "metadata": {
    "timestamp": "2026-02-12T10:30:00Z",
    "requestId": "req-abc-123",
    "version": "v1"
  }
}

Error Response

{
  "success": false,
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "category": "CLIENT_ERROR",
    "message": "Tenant with ID 'xyz' not found",
    "details": {
      "resource": "tenant",
      "identifier": "xyz"
    }
  },
  "metadata": {
    "timestamp": "2026-02-12T10:30:00Z",
    "requestId": "req-abc-123"
  }
}

Error Code Catalog

CodeHTTP StatusCategoryDescription
VALIDATION_FAILED400CLIENT_ERRORRequest body failed validation
AUTHENTICATION_REQUIRED401AUTH_ERRORMissing or invalid JWT token
INSUFFICIENT_PERMISSIONS403AUTH_ERRORUser lacks required permissions
RESOURCE_NOT_FOUND404CLIENT_ERRORRequested resource does not exist
RESOURCE_CONFLICT409CLIENT_ERRORResource already exists
RATE_LIMITED429CLIENT_ERRORTenant exceeded rate limit
INTERNAL_ERROR500SERVER_ERRORUnhandled server error
SERVICE_UNAVAILABLE503SERVER_ERRORDownstream dependency unavailable

2.3.F.3Structured Logging

All services use StructuredLoggingConfig from commons-java, which enriches every log line with context:

{
  "timestamp": "2026-02-12T10:30:00.123Z",
  "level": "INFO",
  "logger": "com.matih.tenant.service.TenantService",
  "service": "tenant-service",
  "tenant_id": "acme-corp",
  "user_id": "user-123",
  "correlation_id": "cor-abc-456",
  "request_id": "req-def-789",
  "trace_id": "abc123def456",
  "span_id": "789ghi",
  "message": "Tenant provisioning started",
  "context": {
    "phase": "CREATE_NAMESPACE",
    "tier": "enterprise"
  }
}

The tenant_id, user_id, and correlation_id fields are automatically injected from the TenantContextHolder and request headers via MDC (Mapped Diagnostic Context). This means every log line in the request's processing chain carries the same context, enabling precise filtering in Loki.


2.3.F.4Health Checks

Each service registers deep health checks via ComponentHealthCheck:

@Component
public class DatabaseHealthCheck implements ComponentHealthCheck {
    @Override
    public Health check() {
        try {
            jdbcTemplate.execute("SELECT 1");
            return Health.up()
                .withDetail("database", "connected")
                .withDetail("responseTime", "2ms")
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("database", "unreachable")
                .withException(e)
                .build();
        }
    }
}

Health checks go beyond simple liveness probes -- they verify that the service can perform its core functions:

CheckVerifies
DatabaseHealthCheckPostgreSQL connection and query execution
RedisHealthCheckRedis connection and PING response
KafkaHealthCheckKafka broker connectivity
DiskSpaceHealthCheckSufficient disk space for logs and temp files
MemoryHealthCheckJVM heap usage below threshold

Kubernetes probes are configured:

# From Helm chart templates
livenessProbe:
  httpGet:
    path: /api/v1/actuator/health/liveness
    port: 8081
  initialDelaySeconds: 30
  periodSeconds: 10
  failureThreshold: 3
 
readinessProbe:
  httpGet:
    path: /api/v1/actuator/health/readiness
    port: 8081
  initialDelaySeconds: 15
  periodSeconds: 5
  failureThreshold: 3

2.3.F.5Pagination and Filtering

All list endpoints support consistent pagination and filtering:

GET /api/v1/users?page=0&size=20&sort=createdAt,desc&status=ACTIVE&search=jane
ParameterTypeDefaultDescription
pageint0Zero-based page number
sizeint20Page size (max 100)
sortstringcreatedAt,descSort field and direction
searchstringnoneFull-text search across indexed fields
Filter paramsvariesnoneEntity-specific filters (e.g., status, tier)

Response includes pagination metadata:

{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 0,
    "size": 20,
    "totalElements": 156,
    "totalPages": 8,
    "hasNext": true,
    "hasPrevious": false
  }
}

2.3.F.6Circuit Breaker Pattern

Inter-service calls use circuit breakers from commons-java:

CLOSED (normal operation)
  |
  +--> 5 consecutive failures --> OPEN (fail fast, return 503)
  |                                |
  |                                +--> after 30s --> HALF_OPEN
  |                                                    |
  |                           3 successful requests -->|
  |                                                    |
  +<----------- Reset to CLOSED <---------------------+

Circuit breaker state is local to each pod (not shared via Redis). This means different replicas may have different circuit breaker states, which is intentional -- it allows partial recovery where healthy replicas can reach the downstream service even if one replica's circuit is open.


2.3.F.7Database Access Patterns

Hibernate Multi-Tenancy

All repository classes automatically scope queries to the current tenant:

// TenantIdentifierResolver routes Hibernate to the correct schema
@Component
public class TenantIdentifierResolver
    implements CurrentTenantIdentifierResolver<String> {
 
    @Override
    public String resolveCurrentTenantIdentifier() {
        return TenantContext.getCurrentTenantIdOrDefault("system");
    }
}
 
// Before each query, Hibernate executes:
// SET search_path TO '{tenant_schema}';
// Then the actual query runs against the correct schema

Optimistic Locking

Entities use @Version for optimistic concurrency control:

@Entity
public class Dashboard {
    @Id
    private UUID id;
 
    @Version
    private Long version;    // Auto-incremented on each update
 
    // If two concurrent updates read version=5,
    // the first update succeeds (sets version=6),
    // the second gets OptimisticLockException
}

Related Sections