Best Practices¶
Building applications with Xcore requires a shift in mindset towards modularity, isolation, and security. Follow these best practices to ensure your ecosystem is scalable, maintainable, and secure.
Architecture & Modularity¶
1. Favor Composition over Inheritance¶
Avoid creating deep inheritance trees between plugins. Instead, expose capabilities via the ServiceContainer and use the PluginRegistry to discover and call other plugins.
2. Keep Plugins Lean¶
Each plugin should have a single, well-defined responsibility. If a plugin starts growing too large, consider splitting it into a "Core" plugin (containing data models and base logic) and multiple "Extension" plugins.
3. Use Dependency Waves¶
Group related plugins using the requires: block in plugin.yaml. This ensures a deterministic boot sequence and allows Xcore to load independent plugins in parallel.
Security & Isolation¶
1. Start with Sandboxing¶
Always develop new or third-party plugins in Sandboxed Mode first. Only move to Trusted Mode if the plugin requires native performance or access to restricted system resources that cannot be exposed via a service.
2. Least Privilege Permissions¶
Be surgical with your permissions: declarations. Avoid db.* if you only need db.users. This minimizes the "blast radius" if a plugin is compromised.
3. Mandatory Signing in Production¶
Always enable strict_trusted: true in your production environment. This prevents "shadow deployments" of unauthorized code and ensures that all trusted code has been audited and signed.
Performance & Scalability¶
1. Avoid Blocking the Event Loop¶
Xcore is built on asyncio. Any synchronous I/O or long-running CPU task in a Trusted plugin will freeze the entire application.
- Use await for all I/O.
- Use run_in_executor or the XWorker service for CPU-bound tasks.
2. Cache Aggressively¶
Use the CacheService to store the results of expensive computations or frequent database queries. The get_or_set pattern is your best friend for maintaining a high-performance hot path.
3. Use emit_sync() for Non-Critical Logic¶
For logging, analytics, or notifications, always use self.ctx.events.emit_sync(). This allows the main request to return to the user immediately while the background tasks are processed.
Error Handling & Reliability¶
1. Consistent Response Formats¶
Always use the ok() and error() helpers. This ensures that callers can handle responses predictably, regardless of which plugin they are calling.
2. Implement Health Checks¶
Don't just rely on the framework. Implement custom health_check() logic in your plugins and services to detect functional degradation (e.g., an external API is down).
3. Idempotent Background Tasks¶
Design your background tasks (via Scheduler or XWorker) to be idempotent. Assume that a task might be executed multiple times due to retries or network failures.
Observability¶
1. Label your Metrics¶
Always add labels (like plugin, tenant_id, or action) to your counters and histograms. This allows you to slice and dice your performance data in Prometheus/Grafana.
2. Structured Logging¶
Never use print() or f-strings for logging. Use the structured logger:
logger.info("Processed order", extra={"order_id": 123, "amount": 45.0})
3. Trace everything¶
Wrap complex logic blocks in manual spans:
with self.ctx.tracer.span("heavy_calculation"): ...
This makes it much easier to identify bottlenecks in distributed traces.