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