Skip to content

Technical Specifications

Technology Stack

LayerTechnologyVersion
RuntimeJava25
FrameworkSpring Boot4.0.3
Plugin systemPF4J (Plugin Framework for Java)
SecuritySpring Security, JWT (HMAC-SHA256)
OAuth2Google OAuth2 via Spring Security OAuth2 Client
ORMSpring Data JPA + Hibernate
Database (production)PostgreSQL16+
Database (tests)H2 (PostgreSQL compatibility mode)
CacheCaffeine
Build toolMaven3.9+
Code generationLombok
JSONJackson

Module Dependencies

metaone-sdk          (no platform dependencies — pure contracts)


metaone-core         (depends on: metaone-sdk, Spring Boot, PF4J, JPA)


crm-extension        (depends on: metaone-sdk, PF4J)
chatwoot-extension   (depends on: metaone-sdk, PF4J)

Extensions must never depend on metaone-core. They depend only on metaone-sdk.

System Requirements

Runtime

RequirementMinimum
Java25+
Memory (host)512 MB heap (2 GB recommended with plugins loaded)
CPU2 cores minimum
Disk (plugins)100 MB per extension JAR

Database

RequirementValue
PostgreSQL version16+
Connections per workspace pool5 (configurable)
Max concurrent workspace pools50 (configurable)

Build

RequirementMinimum
Java (build)25+
Maven3.9+
DockerFor Chatwoot integration tests

Coding Conventions

Package Structure

vn.metaone.core/
├── config/          # Spring @Configuration classes
├── constant/        # Enums and constants
├── controller/      # REST controllers (interface + impl)
│   └── dto/         # Request/response records
├── entity/          # JPA entities
├── repository/      # Spring Data repositories
└── service/         # Business logic
    └── lifecycle/   # Extension lifecycle services

Extension packages follow the same pattern under their own root:

vn.metaone.crm/
vn.metaone.chatwoot/

Key Patterns

Controller split: Every controller is an interface (with @RequestMapping) and a separate *Impl class. The interface is the API contract; the impl holds Spring beans.

Capability handlers must return CapabilityResult<T>: The InProcessCapabilityGateway casts handler return values to CapabilityResult. Returning Map<String, Object> directly causes a ClassCastException at runtime.

java
// ✅ Correct
@Capability(key = "search")
public CapabilityResult<List<ProductDto>> search(Map<String, Object> req, CapabilityContext ctx) {
    return CapabilityResult.success(results);
}

// ❌ Wrong
public Map<String, Object> search(...) { ... }

Manifest is source of truth: Capability handlers do not self-register. The platform routes via ext_capability_registry, which is populated from the manifest at install time. Always update both the handler annotations and the manifest.

Workspace scoping: All repository queries are keyed by workspaceId. Never return data across workspaces.

WorkspaceContext threading: WorkspaceContext uses ThreadLocal. It is set automatically for capability invocations and event handlers. If you spawn custom threads, propagate it manually.

Commit Message Format

Follow Conventional Commits:

feat: add conversation mapping for Chatwoot channels
fix: handle null websiteToken in widget config
docs: update chatwoot testing guide
chore: remove deprecated chat-extension from build
test: add unit tests for CRM event publishing
refactor: extract getOrCreateSystemChannel helper

Pull Request Rules

  • One feature per PR
  • Include tests for new capability handlers
  • Update CHANGELOG.md for all user-facing changes
  • Ensure mvn -DskipTests package passes before opening PR

Deployment

Production Deployment

  1. Build the platform and plugins:

    bash
    mvn clean package -DskipTests
  2. Prepare plugin directory:

    bash
    mkdir -p /opt/metaone/plugins
    cp crm-extension/target/crm-extension-*.jar /opt/metaone/plugins/
    cp chatwoot-extension/target/chatwoot-extension-*.jar /opt/metaone/plugins/
  3. Set required environment variables:

    bash
    export METAONE_JWT_SECRET=<min-32-char-secret>
    export SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/metaone
    export SPRING_DATASOURCE_USERNAME=metaone
    export SPRING_DATASOURCE_PASSWORD=<secret>
    export AUTHORIZED_REDIRECT_URIS=https://app.example.com/oauth2/callback
  4. Run the host:

    bash
    java -jar metaone-core/target/metaone-core-*.jar \
      --metaone.plugins-path=/opt/metaone/plugins
  5. Verify health:

    bash
    curl http://localhost:8080/api/actuator/health

Docker

A docker-compose.dev.yml is provided for local development:

bash
docker-compose -f docker-compose.dev.yml up -d

Starts:

  • PostgreSQL 16 (port 5432) — platform database
  • PostgreSQL 13 + Redis (ports 5433, 6379) — Chatwoot dependencies

Database Initialization

On first startup with spring.jpa.hibernate.ddl-auto: update, Hibernate creates all platform tables automatically. For production, use Flyway or Liquibase for versioned migrations (see TODOS.md).

Plugin Jar Naming

Plugin JARs must be named {pluginId}-{version}.jar and placed in metaone.plugins-path. The PF4J manifest entries in the JAR (Plugin-Id, Plugin-Version, Plugin-Class) must match.

Known Limitations

AreaLimitation
Schema migrationsPlugin databases use ddl-auto: update (not Flyway). Tracked in TODOS P2.
Compatibility checksrequiresPlatformVersion is parsed but not strictly enforced. Tracked in TODOS P2.
Full mvn testFails on Spring context startup in some environments due to missing CapabilityException. Use mvn -pl metaone-core -Dtest=ExtensionInstallerServiceTest test instead. Tracked in TODOS P1.
Naming: tenantId vs workspaceIdSDK event models use tenantId; host code uses workspaceId. Both refer to the same concept. Cleanup tracked in TODOS P2.

MetaOne Platform Documentation