Custom Services¶
While Xcore provides built-in services for Databases, Cache, and Scheduling, you can extend the framework by creating your own Custom Services (also known as Extensions). Custom services follow the same lifecycle as core services and are automatically injected into the service container.
Key Concepts¶
The BaseService Contract¶
A custom service should inherit from xcore.services.base.BaseService and implement the standard lifecycle methods. This ensures that Xcore can manage the service's startup, shutdown, and health monitoring.
The extensions Configuration¶
Custom services are registered in the global xcore.yaml file under the services.extensions block. Xcore uses a dynamic loader to instantiate these classes at boot time.
Practical Guide¶
1. Implementation¶
Create a new Python class that implements the BaseService contract.
2. Registration¶
Register your service in the xcore.yaml file.
| xcore.yaml | |
|---|---|
- Service Key: The name used to retrieve the service from the container.
- Module Path: Format
package.module:ClassName. - Config: Passed as a dictionary to the class constructor.
3. Usage in a Plugin¶
Once registered, you can retrieve your custom service just like a core service.
API Reference¶
BaseService Methods to Implement¶
| Method | Return Type | Description |
|---|---|---|
init() |
None |
Asynchronous initialization (connections, cache warmup). |
shutdown() |
None |
Asynchronous cleanup (closing connections). |
health_check() |
tuple[bool, str] |
Used by the global health monitoring system. |
status() |
dict |
Metadata exposed via the CLI services status command. |
Common Errors & Pitfalls¶
ImportError during Registration
If the module path in xcore.yaml is incorrect or the package is not in the Python path, the framework will log an error and skip the extension.
Check: Verify you can run from myapp.services.email import EmailService in a standard Python shell.
Synchronous Blocking
Like plugins, custom services must be asynchronous. Do not perform blocking I/O inside init() or your service methods.
Status not updated
If you forget to set self._status = ServiceStatus.READY at the end of init(), the ExtensionLoader will assume the service failed to start.
Best Practices¶
Use Environment Variables
Always use ${VAR} substitution in your extensions config for sensitive data like API keys or hostnames.
Granular Health Checks
Your health_check() should be as fast as possible. Avoid heavy queries; prefer a simple "ping" or checking a local connection flag.