as-services/docs/project-implementation.md
devthejo d5febb45c8
Some checks failed
/ build (map[dockerfile:./services/tasks/Dockerfile name:tasks]) (push) Failing after 1m25s
/ deploy (push) Has been skipped
/ build (map[dockerfile:./services/files/Dockerfile name:files]) (push) Failing after 1m34s
/ build (map[dockerfile:./services/app/Dockerfile name:app]) (push) Successful in 2m9s
/ build (map[dockerfile:./services/api/Dockerfile name:api]) (push) Failing after 1m42s
/ build (map[dockerfile:./services/web/Dockerfile name:web]) (push) Failing after 1m18s
/ build (map[dockerfile:./services/watchers/Dockerfile name:watchers]) (push) Failing after 1m22s
/ build (map[dockerfile:./services/hasura/Dockerfile name:hasura]) (push) Successful in 2m3s
chore(init): available sources
2025-04-13 10:34:01 +02:00

14 KiB

Alerte Secours - Modjo Implementation Guide

Project Overview

Alerte Secours is a microservices-based application that leverages the Modjo framework for:

  • API service management
  • Database integration (PostgreSQL)
  • Message queue handling (AMQP)
  • Redis-based caching and queue deduplication
  • OpenAPI and GraphQL integration
  • Monitoring and error tracking (Sentry)

Integration Details

API Service Structure

The API service follows a well-organized structure that implements OpenAPI specifications and REST endpoints:

services/api/src/
├── api/
│   └── v1/
│       ├── formats/           # Custom format definitions
│       ├── operations/        # API endpoint implementations
│       │   ├── alert/        # Alert-related operations
│       │   ├── auth/         # Authentication operations
│       │   ├── external/     # External service integrations
│       │   ├── geoloc/       # Geolocation operations
│       │   ├── info/         # Information endpoints
│       │   └── user/         # User management
│       ├── security/         # Security implementations
│       ├── services/         # Shared services
│       ├── spec-openapi/     # OpenAPI specifications
│       └── validators/       # Input validation
└── tasks/                    # Background task definitions

Key Implementation Patterns

  1. Operation Structure Each API operation follows a consistent pattern:
module.exports = function() {
  // Dependency injection through context
  const sql = ctx.require("postgres")
  const redis = ctx.require("redisHotGeodata")
  
  // Operation implementation
  async function doOperation(req) {
    // Implementation
  }
  
  return [doOperation]
}
  1. Authentication Flow
// Example from auth/login/token.patch.js
async function doAuthLoginToken(req) {
  const { authTokenJwt } = req.body
  // JWT-based authentication with Hasura claims
  const hasuraClaim = {
    "x-hasura-default-role": defaultRole,
    "x-hasura-allowed-roles": roles,
    "x-hasura-user-id": userId.toString()
  }
  // Token generation with expiration
  const jwtData = {
    [claimsNamespace]: hasuraClaim,
    exp: Math.round(new Date(Date.now() + jwtExpirationInHours * 3600000) / 1000)
  }
  return { userBearerJwt: await signJwt(jwtData) }
}
  1. API Schema Generation

The project implements automatic OpenAPI and GraphQL schema generation based on the API service structure:

File Structure Convention

services/api/src/api/v1/
├── operations/                 # API endpoints
│   ├── alert/
│   │   ├── send-alert.patch.js       # Implementation
│   │   └── send-alert.patch.spec.yaml # OpenAPI spec
│   └── auth/
│       ├── login/
│       │   ├── token.patch.js
│       │   └── token.patch.spec.yaml
├── formats/                   # Custom format definitions
├── security/                  # Security schemes
├── services/                  # Shared services
└── validators/               # Input validation

OpenAPI Generation

  • The Modjo OA plugin automatically compiles the API specification from the directory structure
  • Each endpoint implementation (.js) has an adjacent specification file (.yaml)
  • The plugin merges all specs into a single OpenAPI document
  • Swagger UI is automatically generated and served at /swagger

GraphQL Schema Generation

The project automatically generates GraphQL schemas through two mechanisms:

  1. Hasura Database Schema Following strict naming conventions for database operations:

  2. Hasura Table Operations

# Automatically generated from database tables
selectMany${Table}     # Select operations
selectOne${Table}      # Select by primary key
insertOne${Table}      # Insert single record
updateMany${Table}     # Bulk updates
deleteOne${Table}      # Delete by primary key
  1. API Remote Schema
# Generated from REST endpoints
${DataName}.post.js    -> addOne${DataName}
${DataName}.get.js     -> getOne${DataName}
${DataName}.put.js     -> setOne${DataName}
${DataName}.delete.js  -> delOne${DataName}
  1. Real-time Subscriptions
# WebSocket channels
${DataName}.chan/index.sub.js -> subMany${ChannelName}
${DataName}.chan/{id}.sub.js  -> subOne${ChannelName}
  1. OpenAPI to GraphQL Conversion The Modjo OA-GraphQL plugin automatically converts OpenAPI specifications to GraphQL schemas:
// OpenAPI to GraphQL conversion configuration
const { schema } = await createGraphQLSchema(apiSpec, {
  createSubscriptionsFromCallbacks: true,
  baseUrl: `http://${host}:${port}/api/v1/oas`,
  operationIdFieldNames: true,
  fillEmptyResponses: true,
  simpleEnumValues: true,
  headers: (_method, _operationPath, _title, _resolverParams) => ({
    ...omit(req.headers, omitHeaders),
    "x-origin": "GraphQL",
    connection: "close",
  })
})

Key features of the conversion:

  • Automatic type generation from OpenAPI schemas
  • REST endpoints mapped to GraphQL queries/mutations
  • WebSocket callbacks converted to subscriptions
  • Preserved security context and headers
  • GraphQL Playground integration
  1. Relations and Types
# Auto-generated relationship fields
oneTableAsLinkName     # One-to-one named relations
manyTableAsForeignKey  # One-to-many by foreign key

# Auto-generated types from OpenAPI schemas
type Alert {
  id: ID!
  level: AlertLevel!
  location: Point!
  # Fields from OpenAPI schema
}

input AlertSendAlertInput {
  callEmergency: Boolean
  notifyAround: Boolean
  # Fields from OpenAPI request body
}

Implementation Example

  1. REST Endpoint Implementation
// alert/send-alert.patch.js
module.exports = function() {
  const sql = ctx.require("postgres")
  
  async function doAlertSendAlert(req) {
    // Implementation
  }
  return [doAlertSendAlert]
}
  1. OpenAPI Specification
# alert/send-alert.patch.spec.yaml
x-security:
  - auth: ["user"]
requestBody:
  required: true
  content:
    application/json:
      schema:
        type: object
        properties:
          callEmergency:
            type: boolean
          notifyAround:
            type: boolean
          notifyRelatives:
            type: boolean
          uuid:
            type: string
            format: uuid
          level:
            enum: [red, yellow, green]
          subject:
            type: string
          location:
            type: object
            properties:
              type:
                enum: [Point]
              coordinates:
                type: array
                format: location
                items:
                  type: number
        required:
          - level
          - uuid
responses:
  200:
    content:
      application/json:
        schema:
          type: object
          properties:
            alertId:
              type: integer
            accessCode:
              type: string
            code:
              type: string

The OpenAPI specification is automatically generated from these YAML files and exposed through:

// spec-openapi/index.js
const apiSpec = {
  openapi: "3.0.3",
  info: {
    title: "HelpMe Project API",
    version: "1.0.0"
  },
  components: {
    schemas: {},
  },
  paths: {},
  "x-security-sets": {
    auth: ["bearerAuth", "cookieAuth"]
  }
}

Core Service Structure

The project implements Modjo's microservices architecture across several services:

services/
├── api/          # Main API service
├── app/          # Frontend application
├── backoffice/   # Admin interface
├── files/        # File handling service
├── hasura/       # GraphQL engine
├── tasks/        # Background task processing
├── watchers/     # Event monitoring
└── web/          # Web interface

Modjo Configuration

The main API service configuration demonstrates core Modjo integration:

modjo({
  plugins: {
    config: {
      context: (ctx) => {
        ctx.set("customConfig", customConfig)
      }
    },
    oa: {
      pluginName: "oa",
      dependencies: {
        sentry: {},
        postgres: {},
        amqp: {},
        redisQueueDedup: {
          pluginName: "ioredis",
          // Redis configuration
        },
        redisHotGeodata: {
          pluginName: "ioredis",
          // Geodata cache configuration
        }
      }
    }
  },
  dependencies: {
    oapi: {
      pluginName: "microservice-oapi",
      dependencies: {
        oaGraphql: {
          pluginName: "oa-graphql"
        },
        hasura: {
          pluginName: "hasura"
        }
      }
    }
  }
})

Frontend Integration

The React Native frontend application (/lab/alerte-secours/as-app/src) implements a comprehensive GraphQL integration:

src/
├── gql/              # GraphQL core definitions
│   ├── mutations/    # Mutation operations
│   └── queries/      # Query operations
├── scenes/           # Feature-specific components with GraphQL operations
├── hooks/            # Custom Apollo hooks
└── containers/       # Reusable GraphQL-aware components

Key GraphQL Patterns

  1. Real-time Subscriptions
// Alert subscription example
export const ALERTING_SUBSCRIPTION = gql`
  subscription alertingSubscription($cursor: bigint!) {
    alerting(where: { id: { _gt: $cursor } }) {
      id
      alert_id
      user_id
      device_id
    }
  }
`;
  1. Authentication Mutations
// Login mutation
export const LOGIN_USER_TOKEN_MUTATION = gql`
  mutation loginUserToken($authTokenJwt: String!) {
    userBearerJwt: doAuthLoginToken(authTokenJwt: $authTokenJwt) {
      userBearerJwt
    }
  }
`;
  1. Alert Operations
// Send alert mutation
export const SEND_ALERT_MUTATION = gql`
  mutation sendAlert($alertSendAlertInput: AlertSendAlertInput!) {
    alertId: doAlertSendAlert(alertSendAlertInput: $alertSendAlertInput) {
      alertId
      code
      accessCode
    }
  }
`;
  1. Custom Apollo Hooks
// Error handling hook
function useMutationWithError(...args) {
  const [action, ...res] = useMutation(...args);
  return [withError(action), ...res];
}
  1. Real-time Message System
// Message subscription
export const SELECT_STREAM_MESSAGES_SUBSCRIPTION = gql`
  subscription selectStreamMessageSubscription($cursor: Int) {
    message(where: { id: { _gt: $cursor } }) {
      id
      content
      created_at
      user_id
    }
  }
`;

Integration Features

  1. Real-time Updates
  • WebSocket-based subscriptions for alerts
  • Live message streaming
  • Location updates
  1. Authentication Flow
  • JWT-based authentication
  • Token refresh handling
  • Role-based access control
  1. Geolocation Features
  • Real-time location tracking
  • Geocoding integration
  • What3Words integration
  1. Offline Support
  • Apollo cache management
  • Optimistic updates
  • Error handling and retry logic

Code Examples

1. Alert Operation Implementation

// Alert sending implementation
async function doAlertSendAlert(req) {
  const { deviceId, userId } = reqCtx.get("session")
  
  // Database transaction
  await sql.begin(async () => {
    // Insert alert record
    const [{ id }] = await sql`
      INSERT INTO "alert" (...)
      VALUES (...)
      RETURNING id
    `
    // Create alerting record
    await sql`
      INSERT INTO "alerting" (...)
      VALUES (...)
    `
  })
  
  // Parallel task execution
  await async.parallel([
    // Redis geolocation indexing
    async () => redis.geoadd("alert", longitude, latitude, alertId),
    // Background tasks
    async () => notifyAround && addTask(tasks.GEOCODE_ALERT, {...}),
    async () => notifyRelatives && addTask(tasks.RELATIVE_ALERT, {...})
  ])
}

2. Redis Integration

The project uses Redis for two distinct purposes:

  1. Queue Deduplication:
redisQueueDedup: {
  pluginName: "ioredis",
  context: (ctx) => {
    ctx.set("config", {
      redis: {
        host: process.env.REDIS_QUEUE_DEDUP_HOST,
        port: process.env.REDIS_QUEUE_DEDUP_PORT || "6379",
        username: process.env.REDIS_QUEUE_DEDUP_USERNAME || "default",
        password: process.env.REDIS_QUEUE_DEDUP_PASSWORD,
        db: process.env.REDIS_QUEUE_DEDUP_DB || "0"
      }
    })
  }
}
  1. Hot Geodata Caching:
redisHotGeodata: {
  pluginName: "ioredis",
  context: (ctx) => {
    ctx.set("config", {
      redis: {
        host: process.env.REDIS_HOT_GEODATA_HOST,
        port: process.env.REDIS_HOT_GEODATA_PORT || "6379",
        username: process.env.REDIS_HOT_GEODATA_USERNAME || "default",
        password: process.env.REDIS_HOT_GEODATA_PASSWORD,
        db: process.env.REDIS_HOT_GEODATA_DB || "0"
      }
    })
  }
}

2. API Integration

The project uses Modjo's OpenAPI (oa) plugin with GraphQL support:

oapi: {
  pluginName: "microservice-oapi",
  dependencies: {
    oaGraphql: {
      pluginName: "oa-graphql"
    },
    hasura: {
      pluginName: "hasura"
    }
  }
}

Specific Customizations

1. Error Tracking Configuration

Custom Sentry configuration for error tracking:

global.modjoSentryConfig = {
  package: require("../package.json"),
  options: {}
}
require("common/sentry/instrument")

2. CQRS Implementation

The project implements CQRS with eventual consistency:

  • Tasks and requests are made idempotent
  • Event-driven architecture using AMQP
  • Separate services for commands and queries
  • Eventual consistency handled through message queues

Common Issues & Troubleshooting

1. Redis Connection Issues

Problem: Redis connection failures Solution:

  • Verify environment variables are properly set
  • Check Redis server availability
  • Confirm network connectivity between services
  • Validate authentication credentials

2. GraphQL Integration

Problem: GraphQL schema synchronization issues Solution:

  • Ensure Hasura metadata is up to date
  • Verify PostgreSQL connection
  • Check GraphQL endpoint configuration
  • Review schema changes in version control

3. Message Queue Deduplication

Problem: Duplicate message processing Solution:

  • Verify Redis deduplication configuration
  • Check message IDs are unique
  • Ensure proper queue configuration
  • Monitor Redis memory usage

4. Microservices Communication

Problem: Inter-service communication failures Solution:

  • Check service discovery configuration
  • Verify network connectivity
  • Ensure proper authentication between services
  • Monitor service health endpoints