Architecture Documentation

High-level architecture overview for maintainers and contributors.

System Overview

zCrates is a modular Minecraft crate plugin built on Paper 1.21+ with Java 21. The architecture emphasizes:

  • Separation of Concerns - API, implementation, and extensions are distinct

  • Extensibility - Hook system for seamless plugin integration

  • Security - Sandboxed JavaScript execution with restricted API surface

  • Performance - Async database operations, in-memory caching, efficient registries

  • Type Safety - Polymorphic YAML deserialization, strong typing throughout

High-Level Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        USER INTERFACE                            │
│  (Commands, GUIs, Chat Messages, Placed Crates)                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      PRESENTATION LAYER                          │
│  (Commands, Listeners, zMenu Integration)                       │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     BUSINESS LOGIC LAYER                         │
│  (CratesManager, UsersManager, AnimationExecutor)               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      DOMAIN MODEL LAYER                          │
│  (Crate, Reward, Key, Animation, Algorithm, User)               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    INFRASTRUCTURE LAYER                          │
│  (Registries, Script Engine, Storage, Serialization)            │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      EXTERNAL SERVICES                           │
│  (Database, zMenu, Bukkit API, Custom Item Plugins)             │
└─────────────────────────────────────────────────────────────────┘

Module Structure

1. API Module (api/)

Purpose: Public contract for external plugins

Responsibilities:

  • Define interfaces for all core models

  • Expose event system

  • Provide registry access

  • Define extension points (Hook, ItemsProvider, etc.)

Key Characteristics:

  • No implementation details

  • Minimal dependencies (only Bukkit API, annotations)

  • Semantic versioning

  • Backward compatibility guarantees

Exported Packages:

  • fr.traqueur.crates.api - Core interfaces

  • fr.traqueur.crates.api.events - Event system

  • fr.traqueur.crates.api.models - Model interfaces

  • fr.traqueur.crates.api.registries - Registry system

  • fr.traqueur.crates.api.managers - Manager interfaces

  • fr.traqueur.crates.api.hooks - Hook system

  • fr.traqueur.crates.api.providers - Provider interfaces

2. Common Module (common/)

Purpose: Shared implementations used across main plugin and hooks

Responsibilities:

  • Block/Entity display implementations

  • Shared wrapper classes

  • Common utilities

Dependencies:

  • API module

  • Bukkit API

3. Main Plugin Module (src/)

Purpose: Core plugin implementation

Responsibilities:

  • Implement all API interfaces

  • Manage plugin lifecycle

  • Orchestrate business logic

  • Persist data

  • Execute JavaScript

Dependencies:

  • API module

  • Common module

  • Bukkit/Paper API

  • Rhino (JavaScript engine)

  • Structura (YAML serialization)

  • CommandsAPI (command framework)

  • Sarah (ORM)

  • zMenu (GUI system)

  • Reflections (classpath scanning)

4. Hooks Module (hooks/)

Purpose: Optional plugin integrations

Structure:

Characteristics:

  • Each hook is a separate Gradle subproject

  • Isolated dependencies (only required hook targets)

  • Auto-discovered via @AutoHook annotation

  • Gracefully disabled if target plugin missing


Core Subsystems

1. Registry System

Architecture:

Design Decisions:

  1. Static Access via ClassToInstanceMap

    • Avoids dependency injection complexity

    • Type-safe registry retrieval

    • Single source of truth for all registries

  2. FileBasedRegistry Pattern

    • Automatic resource copying from JAR

    • Folder hierarchy with metadata

    • Hot-reload support

    • Consistent loading behavior

  3. Generic ID Type

    • String IDs for file-based registries

    • Enum IDs for runtime registries (DisplayType)

    • Type safety at compile time

2. Manager System

Architecture:

Design Decisions:

  1. ServicesManager Integration

    • Standard Bukkit pattern

    • Automatic lifecycle management

    • Accessible from any plugin

  2. Single Responsibility

    • CratesManager: crate operations

    • UsersManager: user data

    • Clear separation of concerns

  3. In-Memory State

    • Active openings in CratesManager

    • User cache in UsersManager

    • Placed crates loaded per chunk

3. Script Engine System

Architecture:

Design Decisions:

  1. Single Engine Instance

    • Shared Rhino Context across all scripts

    • Reduced memory footprint

    • Consistent security settings

  2. Isolated Scopes

    • Each script execution uses new scope

    • Null parent (no prototype chain access)

    • Prevents cross-script interference

  3. Wrapper Pattern

    • Controlled API surface

    • Type-safe method exposure

    • No direct Java object access

  4. Security First

    • Interpreter mode (no bytecode)

    • Method blacklist via WrapFactory

    • No Java package access

    • No reflection

4. Animation System

Architecture:

Design Decisions:

  1. Phase-Based Execution

    • Sequential phase execution

    • Independent timing per phase

    • Clean state transitions

  2. SpeedCurve Integration

    • Mathematical easing functions

    • Applied to progress value

    • Smooth visual transitions

  3. BukkitRunnable Scheduling

    • Sync with server tick rate

    • Cancel-safe execution

    • Automatic cleanup

5. Storage System

Architecture:

Design Decisions:

  1. Sarah ORM

    • Lightweight abstraction

    • Multiple database support

    • Migration management

    • Connection pooling

  2. Repository Pattern

    • Separation of persistence logic

    • Testable data access

    • Async by default

  3. DTO Pattern

    • Database schema independence

    • Clear domain/persistence boundary

    • Easy schema evolution

  4. Async Operations

    • Non-blocking database access

    • CompletableFuture API

    • Main thread safety

6. Display System

Architecture:

Design Decisions:

  1. Generic Type Parameter

    • Type-safe value matching

    • Compile-time validation

    • Flexible display types

  2. Factory Pattern

    • Encapsulates creation logic

    • Validation before creation

    • Tab completion support

  3. Chunk PDC Storage

    • Persistent across restarts

    • Automatic chunk association

    • No separate database table

  4. Lazy Loading

    • Displays created on chunk load

    • Removed on chunk unload

    • Memory efficient


Data Flow

Crate Opening Flow

User Data Flow

Configuration Loading Flow


Plugin Lifecycle

Initialization Sequence

Shutdown Sequence

Reload Sequence


Design Decisions

1. Why Rhino Instead of Nashorn/GraalVM?

Decision: Use Rhino JavaScript engine

Rationale:

  • Security: Fine-grained control over Java access

  • Compatibility: Works on all Java versions (11+)

  • Sandbox: Built-in sandboxing with initSafeStandardObjects()

  • Performance: Interpreter mode sufficient for animation/algorithm scripts

Trade-offs:

  • Not ES2015+ compliant (limited ES6 support)

  • Slower than GraalVM for compute-intensive tasks

  • No native Promise support

2. Why Static Registry Access?

Decision: Use ClassToInstanceMap for static registry access

Rationale:

  • Simplicity: No dependency injection framework needed

  • Type Safety: Compile-time verification of registry types

  • Accessibility: Accessible from any plugin code

  • Singleton: Single source of truth

Trade-offs:

  • Global state (harder to test in isolation)

  • Cannot swap implementations at runtime

  • Requires manual registration

3. Why Chunk PDC for Placed Crates?

Decision: Store placed crates in Chunk PDC

Rationale:

  • Automatic Association: Chunks own their crates

  • Persistence: Survives server restarts

  • Performance: No database queries for crate lookup

  • Simplicity: No separate placed_crates table

Trade-offs:

  • Limited to chunk-sized queries (can't query all crates in world easily)

  • PDC size limits (unlikely to hit in practice)

  • Chunk load required for data access

4. Why ServicesManager for Managers?

Decision: Register managers via Bukkit ServicesManager

Rationale:

  • Standard Pattern: Follows Bukkit conventions

  • Discovery: Other plugins can find managers

  • Lifecycle: Bukkit manages service lifecycle

  • Type Safety: Retrieval by class

Trade-offs:

  • Couples to Bukkit API

  • Limited lifecycle control

  • No priority/ordering guarantees

5. Why Polymorphic YAML with Structura?

Decision: Use Structura for YAML deserialization

Rationale:

  • Type Safety: Compile-time validation of YAML structure

  • Polymorphism: @Polymorphic annotation for type field

  • Validation: Built-in validation (@Options, @DefaultInt, etc.)

  • Maintainability: YAML changes reflected in code

Trade-offs:

  • Additional dependency

  • Learning curve for contributors

  • Less flexible than manual parsing


Performance Considerations

1. Memory Management

User Cache:

  • Cleared on player quit (prevents memory leaks)

  • ConcurrentHashMap for thread-safe access

  • No unbounded growth

Registry Storage:

  • HashMap-based (O(1) lookup)

  • Immutable after loading (no synchronization needed)

  • Cleared on reload (prevents memory bloat)

Animation State:

  • Removed immediately after completion

  • No persistent animation history

  • Bounded by concurrent player count

2. Database Optimization

Connection Pooling:

  • HikariCP for connection management

  • Configurable pool size

  • Connection validation

Async Operations:

  • All DB operations use CompletableFuture

  • No main thread blocking

  • Batch operations where possible

Caching:

  • User data cached in memory

  • Keys stored locally (no query per check)

  • Opening history loaded on-demand

3. JavaScript Performance

Interpreter Mode:

  • No bytecode compilation overhead

  • Lower memory usage

  • Acceptable performance for animations/algorithms

Scope Reuse:

  • Scopes created per execution (not per phase)

  • Shared Rhino Context across all scripts

  • Minimal GC pressure

Wrapper Objects:

  • Lightweight wrappers (no heavy computation)

  • Direct method calls (no reflection)

  • Cached references where possible

4. Event System

Listener Registration:

  • Single listener instances (not per crate)

  • Priority-based execution

  • Ignore if not handling event

Event Frequency:

  • CratePreOpenEvent: Once per open attempt

  • CrateOpenEvent: Once per successful open

  • RewardGeneratedEvent: Once per opening + rerolls

  • RewardGivenEvent: Once per completion


Security Architecture

1. JavaScript Sandbox

Threat Model:

  • Malicious server admin uploading dangerous scripts

  • Exploiting Java reflection to bypass security

  • Accessing file system or network

  • Escaping sandbox via prototype pollution

Mitigations:

  • No Java Package Access: initSafeStandardObjects() blocks java.*

  • Method Blacklist: Custom WrapFactory blocks dangerous methods

  • Null Parent Scopes: Prevents prototype chain manipulation

  • Interpreter Mode: No bytecode execution (no JIT exploits)

  • Wrapper Objects: Controlled API surface

Residual Risks:

  • Infinite loops (no timeout mechanism)

  • Memory exhaustion (no heap limits)

  • Malicious animation logic (e.g., inventory spam)

2. Inventory Security

Threat Model:

  • Script manipulating unauthorized slots

  • Duplication exploits via inventory manipulation

  • Item theft via inventory swaps

Mitigations:

  • Slot Authorization: Only authorized slots can be modified

  • Wrapper Methods: Controlled inventory access

  • State Validation: Animation state tracked by manager

3. Data Security

Threat Model:

  • SQL injection via user input

  • Race conditions in user data

  • Data loss on crashes

Mitigations:

  • Prepared Statements: Sarah ORM uses prepared statements

  • Concurrent Collections: Thread-safe user cache

  • Transactions: Database operations in transactions

  • Async Saves: Non-blocking persistence

4. Permission System

Threat Model:

  • Unauthorized crate access

  • Permission bypass via exploits

Mitigations:

  • PermissionCondition: Checks before opening

  • Event Cancellation: External plugins can prevent opens

  • Key Validation: Keys verified before consumption


Future Considerations

Scalability

Potential Bottlenecks:

  1. User cache growth (many concurrent players)

  2. Database connection pool exhaustion

  3. Animation executor (many concurrent animations)

  4. Chunk PDC size limits

Mitigation Strategies:

  1. TTL-based cache eviction

  2. Configurable pool size

  3. Queue system for concurrent animations

  4. Migration to separate placed_crates table if needed

Extensibility

Extension Points:

  1. Custom OpenConditions via polymorphic registry

  2. Custom Rewards via polymorphic registry

  3. Custom DisplayTypes via factory registry

  4. Custom Hooks via @AutoHook annotation

  5. Custom ItemsProviders via provider registry

API Stability:

  • Semantic versioning for API module

  • Deprecation warnings before removal

  • Backward compatibility for configuration

Monitoring

Observability:

  1. Debug logging for troubleshooting

  2. Event system for external tracking

  3. Metrics (opening counts, reward distribution)

Health Checks:

  1. Database connection validation

  2. Registry population verification

  3. Script engine health checks


Conclusion

The zCrates architecture prioritizes:

  1. Modularity - Clear separation between API, implementation, and extensions

  2. Security - Sandboxed JavaScript, controlled API surface

  3. Performance - Async operations, efficient caching, optimized data structures

  4. Extensibility - Hook system, polymorphic types, provider pattern

  5. Maintainability - Type safety, clear abstractions, comprehensive documentation

Last updated