Skip to main content

Internal Architecture

Overview

Q01 Core APIs implement a CQRS (Command Query Responsibility Segregation) pattern with three microservices:

  1. CoreService (Go/Buffalo) - API Gateway
  2. CoreQuery (PHP/Symfony) - Read Operations
  3. CoreWrite (PHP/Symfony) - Write Operations

From the user perspective:

  • All requests go to CoreService endpoints only
  • HTTP method determines operation type
  • Internal routing is completely transparent

Benefits:

  • ✅ Read/write optimization (separate databases possible)
  • ✅ Independent scaling (scale reads and writes separately)
  • ✅ Clear separation of concerns
  • ✅ Improved maintainability
  • ✅ Security isolation

Architecture Diagram

┌─────────────────────────────────────────────────────┐
│ Applications │
│ (Web Apps, Mobile Apps, Backend Services, etc.) │
└──────────────────────┬──────────────────────────────┘

│ HTTP/HTTPS

┌──────────────────────▼──────────────────────────────┐
│ CoreService (API Gateway) │
│ Go/Buffalo - Port 8080 │
│ │
│ • CORS handling │
│ • SSL/TLS termination │
│ • Request logging │
│ • Route-based routing logic │
│ • Error normalization │
└──────────────┬────────────────────┬─────────────────┘
│ │
│ GET requests │ POST/PUT/PATCH/DELETE
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ CoreQuery │ │ CoreWrite │
│ PHP/Symfony │ │ PHP/Symfony │
│ Port 8081 │ │ Port 8082 │
│ │ │ │
│ • TB_DIM metadata │ │ • TB_DIM metadata │
│ • TB_VAR, TB_COST │ │ • CRUD operations │
│ • TB_MENU grants │ │ • TREC lifecycle │
│ • Context filtering │ │ • Cascade ops │
│ • Field visibility │ │ • OUTBOX events │
│ • Read-only SQL │ │ • Pre-insert funcs │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
│ │
▼ ▼
┌──────────────────────────────────────────────────┐
│ Database Layer │
│ │
│ • TB_META (metadata database) │
│ • TB_DATA (data database) │
│ • TB_AUTH (authentication database) │
│ │
│ MySQL / Oracle / PostgreSQL │
└──────────────────────────────────────────────────┘

│ RabbitMQ Events

┌──────────────────────────────────────────────────┐
│ Event Subscribers │
│ │
│ • search-indexer (Elasticsearch) │
│ • cache-invalidator (Redis) │
│ • analytics-writer (Data warehouse) │
│ • notification-sender (Email/SMS) │
│ • webhook-dispatcher (External APIs) │
└──────────────────────────────────────────────────┘

CoreService (API Gateway)

Technology Stack

  • Language: Go 1.19+
  • Framework: Buffalo v1.1.0
  • Port: 8080 (default)
  • Repository: q01sdk/mscoreservice/dkcoreservice

Responsibilities

  1. Single Entry Point

    • All applications call CoreService endpoints
    • Public-facing API surface
  2. Request Routing

    • Route-based routing (named routes, not path matching)
    • Intelligent routing to QUERY_API or WRITE_API
  3. Cross-Cutting Concerns

    • CORS handling
    • SSL/TLS termination
    • Request/response logging
    • Error normalization
  4. No Business Logic

    • Pure gateway - no data validation
    • No metadata interpretation
    • No database access

Routing Logic

Route-based routing (not path-based):

func RoutingDefault(c buffalo.Context) error {
// Get route name from context
currRouteStruct := buffalo.RouteInfo{}
currRoute := c.Data()["current_route"]
jsonData, _ := json.Marshal(currRoute)
json.Unmarshal(jsonData, &currRouteStruct)
routeName := currRouteStruct.PathName

resource := c.Request().URL.Path

// Route based on named route
switch routeName {
// READ OPERATIONS → CoreQuery
case "coreIndexPath", // GET /api/v4/core/{dim}
"coreShowPath", // GET /api/v4/core/{dim}/{id}
"coreFieldsPath", // GET /api/v4/core/{dim}/fields
"coreMenuPath": // GET /api/v4/core/menu
code, contentType, data = routingGet(c, resource, "QUERY_API")

// WRITE OPERATIONS → CoreWrite
case "coreCreatePath": // POST /api/v4/core/{dim}
code, contentType, data = routingPost(c, resource, "WRITE_API")

case "coreUpdatePath": // PUT /api/v4/core/{dim}/{id}
code, contentType, data = routingPut(c, resource, "WRITE_API")

case "corePatchPath": // PATCH /api/v4/core/{dim}/{id}
code, contentType, data = routingPatch(c, resource, "WRITE_API")

case "coreDestroyByIDPath": // DELETE /api/v4/core/{dim}/{id}
code, contentType, data = routingDelete(c, resource, "WRITE_API")
}

return c.Render(code, r.JSON(data))
}

Key characteristics:

  • Routing based on route name (e.g., "coreIndexPath")
  • Route names end with "Path" suffix
  • Same URL path can route to different backends based on route name
  • No path pattern matching - purely based on named routes

Backend Destination Resolution

func getDestination(destination string) (string, string) {
var host string
var port string

if destination == "QUERY_API" {
host = os.Getenv("R_API_HOST") // CoreQuery host
port = os.Getenv("R_API_PORT") // CoreQuery port
} else {
host = os.Getenv("W_API_HOST") // CoreWrite host
port = os.Getenv("W_API_PORT") // CoreWrite port
}

return host, port
}

func getUrl(destination string, resource string) string {
host, port := getDestination(destination)

apiUrl := host
if port != "" {
apiUrl = host + ":" + port
}

return apiUrl + path.Clean(resource)
}

HTTP Method Handlers

GET requests:

func routingGet(c buffalo.Context, resource string, destination string) (int, string, *interface{}) {
apiurl := getUrl(destination, resource)

// Create HTTP GET request
api_req, _ := http.NewRequest("GET", apiurl, nil)

// Clone headers from original request
api_req.Header = c.Request().Header

// Clone query parameters
api_req.URL.RawQuery = c.Request().URL.Query().Encode()

// Forward request
core_api_client := &http.Client{}
api_resp, _ := core_api_client.Do(api_req)

// Read response
data, _ := io.ReadAll(api_resp.Body)

return api_resp.StatusCode, api_resp.Header.Get("Content-Type"), &data
}

POST/PUT/PATCH requests:

func routingPost(c buffalo.Context, resource string, destination string) (int, string, *interface{}) {
apiurl := getUrl(destination, resource)

// Read request body
body, _ := io.ReadAll(c.Request().Body)

// Create HTTP POST request
api_req, _ := http.NewRequest("POST", apiurl, bytes.NewBuffer(body))

// Clone headers
api_req.Header = c.Request().Header

// Clone query parameters
api_req.URL.RawQuery = c.Request().URL.Query().Encode()

// Forward request
core_api_client := &http.Client{}
api_resp, _ := core_api_client.Do(api_req)

// Parse response
var result interface{}
json.NewDecoder(api_resp.Body).Decode(&result)

return api_resp.StatusCode, "application/json", &result
}

Environment Variables

CoreQuery (Read API):

  • R_API_HOST - CoreQuery hostname (e.g., corequery)
  • R_API_PORT - CoreQuery port (e.g., 8081)
  • R_API_VERSION - Read API version (e.g., v4)

CoreWrite (Write API):

  • W_API_HOST - CoreWrite hostname (e.g., corewrite)
  • W_API_PORT - CoreWrite port (e.g., 8082)
  • W_API_VERSION - Write API version (e.g., v4)

CoreService:

  • GO_ENV - Environment (development/production)
  • ADDR - Bind address (default: 0.0.0.0)
  • PORT - Server port (default: 8080)

Binary/Media Handling

Special handler for binary responses:

case "coreMediaPath",
"coreMediaPublicPath":
code, contentType, data = routingGetImage(c, resource, "QUERY_API")

func routingGetImage(c buffalo.Context, resource string, destination string) (int, string, []byte) {
apiurl := getUrl(destination, resource)

api_req, _ := http.NewRequest("GET", apiurl, nil)
api_req.Header = c.Request().Header
api_req.URL.RawQuery = c.Request().URL.Query().Encode()

core_api_client := &http.Client{}
api_resp, _ := core_api_client.Do(api_req)

// Read binary data
data, _ := io.ReadAll(api_resp.Body)

return api_resp.StatusCode, api_resp.Header.Get("Content-Type"), data
}

CoreQuery (Read Service)

Technology Stack

  • Language: PHP 8.0+
  • Framework: Symfony 6.x
  • Port: 8081 (default)
  • Repository: q01sdk/mscorequery/dkcorequery

Responsibilities

  1. Metadata-Driven Queries

    • TB_DIM, TB_VAR, TB_COST, TB_MENU interpretation
    • Runtime schema resolution
  2. Read-Only Operations

    • GET endpoints only
    • SELECT queries only
    • No database modifications
  3. Multi-Level Permissions

    • peso (user level) filtering
    • COD_ON_OFF field visibility
    • TB_MENU grant checking
    • Context filtering (source, ambiente, centro_dett)
  4. Field Visibility

    • COD_ON_OFF bitmask evaluation
    • L (list), D (detail), R (search) flags
    • Dynamic field inclusion/exclusion
  5. Query Optimization

    • Index-aware query generation
    • Efficient JOIN construction
    • Pagination support

Core Components

Main Query Engine:

  • src/Core/Modules.php - 4200+ lines of query logic
  • Metadata-driven SQL generation
  • Field visibility enforcement
  • Context filtering application

Request Validation:

  • src/EventDispatcher/Subscriber/RequestValidatorSubscriber.php
  • Token validation
  • Grant checking
  • Required parameter validation

CoreWrite (Write Service)

Technology Stack

  • Language: PHP 8.0+
  • Framework: Symfony 6.x
  • Port: 8082 (default)
  • Repository: q01sdk/mscorewrite/dkcorewrite

Responsibilities

  1. CRUD Operations

    • INSERT (POST)
    • UPDATE (PUT)
    • PARTIAL UPDATE (PATCH)
    • DELETE (DELETE)
  2. Entity Lifecycle

    • TREC state management (N→M→C)
    • Cascade operations (INSERT_CASCADE, UPDATE_CASCADE, DELETE_CASCADE)
    • Soft delete support
  3. Pre-Insert Functions

    • uuid, md5, counter, slugify, sequence
    • Metadata-driven field initialization
  4. Event Sourcing

    • OUTBOX pattern implementation
    • RabbitMQ event publishing
    • At-least-once delivery guarantee
  5. Transaction Management

    • ACID compliance
    • Row-level locking
    • Rollback on failure

Core Components

CRUD Engine:

  • src/Core/DbInterface/DataStore.php - 3354 lines
  • INSERT, UPDATE, PATCH, DELETE operations
  • Cascade logic
  • OUTBOX event generation

Request Validation:

  • src/EventDispatcher/Subscriber/RequestValidatorSubscriber.php
  • Token validation
  • Grant checking (write permissions)
  • Required field validation
  • COD_ON_OFF flag checking

Request Flow

Read Request Flow

1. Client → CoreService
GET https://api.q01.io/api/v4/core/products?limit=10

2. CoreService → CoreQuery
• Route name: "coreIndexPath"
• Destination: QUERY_API
• Forwards to: http://corequery:8081/api/v4/core/products?limit=10

3. CoreQuery Processing
• Validate token (JWT)
• Check TB_MENU grants (read permission)
• Load TB_DIM metadata (dimension: PRD)
• Load TB_VAR metadata (field definitions)
• Apply context filters (source, peso, ambiente, centro_dett)
• Evaluate COD_ON_OFF flags (L=list visible)
• Generate SQL: SELECT PRD_ID, XPRD01, XPRD02... FROM TB_ANAG_PRD00 WHERE source=? AND TREC='N' LIMIT 10
• Execute query
• Build JSON response

4. CoreQuery → CoreService
HTTP 200 OK
{"data": [...], "total": 150}

5. CoreService → Client
HTTP 200 OK
{"data": [...], "total": 150}

Write Request Flow

1. Client → CoreService
POST https://api.q01.io/api/v4/core/products
{"XPRD01": "Widget", "XPRD02": 19.99}

2. CoreService → CoreWrite
• Route name: "coreCreatePath"
• Destination: WRITE_API
• Forwards to: http://corewrite:8082/api/v4/core/products

3. CoreWrite Processing
• Validate token (JWT)
• Check TB_MENU grants (create permission)
• Load TB_DIM metadata (dimension: PRD)
• Load TB_VAR metadata (field definitions)
• Validate required fields
• Apply context (source, peso, ambiente, centro_dett)
• Execute pre-insert functions (uuid, counter, etc.)
• Begin transaction
• INSERT INTO TB_ANAG_PRD00 (...)
• INSERT INTO OUTBOX (event_type='ProductCreated', ...)
• Commit transaction
• Build JSON response

4. CoreWrite → CoreService
HTTP 201 Created
{"PRD_ID": 123, "XPRD01": "Widget", ...}

5. CoreService → Client
HTTP 201 Created
{"PRD_ID": 123, "XPRD01": "Widget", ...}

6. Background: OUTBOX Publisher → RabbitMQ
• Poll OUTBOX table
• Publish event to fanout exchange
• Subscribers: search-indexer, cache-invalidator, etc.

CQRS Benefits

Read Optimization

CoreQuery can:

  • Use read replicas
  • Implement aggressive caching
  • Optimize for query performance
  • Scale independently

Example:

┌──────────────┐       ┌──────────────┐
│ CoreQuery │──────▶│ Read Replica │
│ (3 pods) │ │ (MySQL) │
└──────────────┘ └──────────────┘

Write Optimization

CoreWrite can:

  • Use write master
  • Implement write-ahead logging (OUTBOX)
  • Optimize for transaction throughput
  • Scale independently

Example:

┌──────────────┐       ┌──────────────┐
│ CoreWrite │──────▶│ Write Master │
│ (2 pods) │ │ (MySQL) │
└──────────────┘ └──────────────┘

│ Events

┌──────────────┐
│ RabbitMQ │
│ Fanout │
└──────────────┘

Deployment Architecture

Docker Compose Example

version: '3.8'

services:
coreservice:
image: q01/coreservice:latest
ports:
- "8080:8080"
environment:
R_API_HOST: http://corequery
R_API_PORT: 8081
R_API_VERSION: v4
W_API_HOST: http://corewrite
W_API_PORT: 8082
W_API_VERSION: v4
GO_ENV: production
depends_on:
- corequery
- corewrite

corequery:
image: q01/corequery:latest
ports:
- "8081:8081"
environment:
DATABASE_HOST: mysql-read
DATABASE_PORT: 3306
DATABASE_NAME: q01_data
DATABASE_USER: q01_reader
DATABASE_PASSWORD: ${DB_READ_PASSWORD}
depends_on:
- mysql-read

corewrite:
image: q01/corewrite:latest
ports:
- "8082:8082"
environment:
DATABASE_HOST: mysql-write
DATABASE_PORT: 3306
DATABASE_NAME: q01_data
DATABASE_USER: q01_writer
DATABASE_PASSWORD: ${DB_WRITE_PASSWORD}
RABBITMQ_HOST: rabbitmq
RABBITMQ_PORT: 5672
depends_on:
- mysql-write
- rabbitmq

mysql-read:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: q01_data

mysql-write:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: q01_data

rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"

Kubernetes Example

apiVersion: apps/v1
kind: Deployment
metadata:
name: coreservice
spec:
replicas: 3
selector:
matchLabels:
app: coreservice
template:
metadata:
labels:
app: coreservice
spec:
containers:
- name: coreservice
image: q01/coreservice:latest
ports:
- containerPort: 8080
env:
- name: R_API_HOST
value: "http://corequery-service"
- name: R_API_PORT
value: "8081"
- name: W_API_HOST
value: "http://corewrite-service"
- name: W_API_PORT
value: "8082"
---
apiVersion: v1
kind: Service
metadata:
name: coreservice
spec:
type: LoadBalancer
selector:
app: coreservice
ports:
- port: 80
targetPort: 8080

Performance Considerations

Latency

Additional hop:

  • Client → CoreService: ~5ms
  • CoreService → CoreQuery/CoreWrite: ~2ms
  • Total overhead: ~7ms

Mitigation:

  • Deploy all services in same region/VPC
  • Use connection pooling
  • Enable HTTP keep-alive

Throughput

CoreService bottleneck:

  • Go is highly concurrent (goroutines)
  • Can handle 10,000+ req/sec per pod
  • Scales horizontally

Monitoring:

# Check CoreService throughput
curl http://coreservice:8080/metrics | grep http_requests_total

Best Practices

✅ DO:

Use environment variables for configuration:

# ✅ Good - external configuration
export R_API_HOST=http://corequery-prod
export W_API_HOST=http://corewrite-prod

Scale services independently:

# ✅ Good - scale reads more than writes
kubectl scale deployment corequery --replicas=5
kubectl scale deployment corewrite --replicas=2

Monitor all three services:

# ✅ Good - comprehensive monitoring
curl http://coreservice:8080/health
curl http://corequery:8081/health
curl http://corewrite:8082/health

❌ DON'T:

Don't call CoreQuery/CoreWrite directly:

# ❌ Bad - bypass CoreService
curl http://corequery:8081/api/v4/core/products

# ✅ Good - through CoreService
curl http://coreservice:8080/api/v4/core/products

Don't hardcode backend URLs:

// ❌ Bad - hardcoded
apiUrl := "http://corequery:8081" + resource

// ✅ Good - environment variable
apiUrl := os.Getenv("R_API_HOST") + resource

Summary

  • ✅ CoreService is the single public-facing API gateway
  • ✅ Route-based routing (named routes, not path matching)
  • ✅ CoreQuery handles all read operations (GET)
  • ✅ CoreWrite handles all write operations (POST/PUT/PATCH/DELETE)
  • ✅ CQRS enables independent scaling and optimization
  • ✅ Environment variables control routing
  • ✅ All business logic in CoreQuery/CoreWrite, not CoreService
  • ✅ Clean separation of concerns

Key Takeaways:

  1. Users only call CoreService endpoints
  2. Internal routing is completely transparent
  3. Named routes determine destination (not HTTP method alone)
  4. CQRS enables read/write optimization
  5. Scale CoreQuery and CoreWrite independently
  6. Monitor all three services
  7. Deploy in same region for low latency
  8. Never bypass CoreService in production