Feature-Oriented Event-Driven Application Skeleton
A Python-based asynchronous application framework designed for building scalable, event-driven systems using a feature-oriented architecture. This skeleton provides a solid foundation for building complex applications with clear separation of concerns, dependency injection, and event-driven communication patterns.
Overview
This project implements a feature-oriented architecture where functionality is organized into independent, self-contained features that communicate through an event-driven model. Each feature is responsible for its own initialization, configuration, and bootstrap logic, making the system highly modular and testable.
Key Concepts
- Feature-Oriented: Application logic is divided into independent features, each with its own provider for configuration and lifecycle management
- Event-Driven: Features communicate asynchronously through an event emitter
- Async-First: Built on Python's
asynciofor high-performance concurrent operations - Dependency Injection: Uses the
punqcontainer for loose coupling and testability - Kernel-Centric: Core infrastructure (application lifecycle, messaging, events) lives in the
kernelnamespace
Project Structure
FeatureOrientedSkeleton/
├── kernel/ # Core framework and infrastructure
│ ├── application/ # Application lifecycle management
│ │ ├── application.py # Abstract base application class
│ │ ├── kernel.py # Abstract kernel (initialization point)
│ │ ├── stop_event.py # Graceful shutdown signaling
│ │ ├── enum/ # Core enumerations
│ │ └── manager/ # Event managers
│ ├── event/ # Event handling infrastructure
│ │ ├── listener.py # Base listener interface
│ │ └── registrar.py # Event listener registration mixin
│ ├── feature/ # Feature provider interface
│ │ └── provider.py # Abstract feature provider
│ └── rabbitmq/ # RabbitMQ client and connection
│ ├── client.py # RabbitMQ messaging client
│ ├── enum/ # RabbitMQ-specific types
│ └── exception/ # RabbitMQ exceptions
│
├── shared/ # Shared utilities and implementations
│ ├── application/ # Concrete application implementations
│ │ ├── rabbitmq_application.py # RabbitMQ-based application
│ │ ├── rabbitmq_kernel.py # RabbitMQ kernel with DI setup
│ │ └── factory/ # Factory patterns for common objects
│ ├── event/ # Shared event types and structures
│ ├── service/ # Cross-cutting services
│ ├── manager/ # Utilities (argument parsing, etc)
│ └── view/ # View models and configurations
│
├── feature/ # Application features (domain logic)
│ ├── listen_rabbitmq/ # Feature: Listen to RabbitMQ messages
│ │ ├── listen_rabbitmq.py # Feature provider
│ │ ├── consumer/ # Message consumption logic
│ │ ├── service/ # Business logic and services
│ │ └── type/ # Domain types and models
│ │
│ ├── log_bus_message/ # Feature: Log received messages
│ │ ├── log_bus_message.py # Feature provider
│ │ └── listener/ # Event listeners
│ │
│ ├── prepare_error_notification/ # Feature: Prepare error notifications
│ ├── send_telegram_notification/ # Feature: Send Telegram notifications
│ └── [other-features]/ # Additional features
│
├── entry/ # Application entry points
│ └── log_user_messages.py # CLI entry script
│
├── typings/ # Type stubs for third-party libraries
│ ├── aioredis/
│ ├── aioredlock/
│ └── punq/
│
├── deploy/ # Deployment configurations
│ ├── local/ # Local development (Docker Compose)
│ │ ├── compose.yaml
│ │ └── python.Dockerfile
│ └── main/ # Production deployment
│ ├── compose.yaml
│ └── python.Dockerfile
│
├── examples/ # Example payloads and usage
├── Makefile # Development commands
├── pyrightconfig.json # Type checking configuration
├── requirements.txt # Python dependencies
├── .env.example # Environment variables template
└── README.md # This file
Architecture Patterns
1. Feature Provider Pattern
Each feature must implement a Feature Provider - a class that inherits from Provider (and optionally Registrar mixin):
from typing import override
from kernel.feature.provider import Provider
from kernel.event.registrar import Registrar
from shared.event.type import Type as EventType
class MyFeature(Provider, Registrar):
@override
def register(self) -> None:
"""
Called during DI container initialization.
- Register classes in the container
- Load configurations
- Set up initial state
"""
self._container.register(MyService)
@override
def bootstrap(self) -> None:
"""
Called after all features are registered.
- Start background tasks
- Attach event listeners
- Initialize runtime state
"""
self.add_event_listener(EventType.MESSAGE_RECEIVED, MyEventListener)
2. Event Listener Pattern
Create an event listener that inherits from Listener[T]:
from kernel.event.listener import Listener
from shared.event.type import Type as EventType
class MyEventListener(Listener[MyEventType]):
async def handle(self, event: str, payload: MyEventType) -> None:
# Process the event
pass
Attach it in your feature's bootstrap() method using the Registrar mixin.
3. Dependency Injection
All dependencies are resolved through the punq Container:
from punq import Container
class MyService:
def __init__(self, container: Container) -> None:
self._container = container
def do_something(self) -> None:
dependency = self._container.resolve(SomeDependency)
Quick Start
1. Clone and Setup
git clone <repository>
cd FeatureOrientedSkeleton
cp .env.example .env
2. Using Makefile (Recommended)
# Build and start all services
make up
# View application logs
make logs
# Stop services
make stop
# Clean up containers and volumes
make clean
3. Manual Docker Compose
# Start services
docker-compose -f deploy/local/compose.yaml up
# Stop services
docker-compose -f deploy/local/compose.yaml down
Running the Example
The project includes an example application that listens to RabbitMQ messages and logs them:
make up
# Application starts and listens on queue 'application.skeleton'
# Send messages and view logs
make logs
Creating a New Feature
Step 1: Create Feature Directory
mkdir -p feature/my_feature/{service,listener,type,consumer}
touch feature/my_feature/__init__.py
Step 2: Create Feature Provider
# feature/my_feature/my_feature.py
from typing import override
from kernel.feature.provider import Provider
from kernel.event.registrar import Registrar
class MyFeature(Provider, Registrar):
@override
def register(self) -> None:
# Register dependencies and services
pass
@override
def bootstrap(self) -> None:
# Attach event listeners if needed
pass
Step 3: Create Event Listener (if needed)
# feature/my_feature/listener/my_listener.py
from kernel.event.listener import Listener
class MyListener(Listener[MyEventType]):
async def handle(self, event: str, payload: MyEventType) -> None:
# Handle the event
pass
Step 4: Use in Entry Script
# entry/my_listener.py
import asyncio
from shared.application.factory.application import Application as ApplicationFactory
from feature.my_feature import MyFeature
if __name__ == "__main__":
factory = ApplicationFactory()
application = factory.create_rabbitmq([
MyFeature,
])
asyncio.run(application.launch(main))
Message Format
Messages must conform to a specific JSON format:
{
"service": "service_name",
"environment": "production",
"exception": "ErrorType",
"message": "Error message text",
"type": "error",
"datetime": "2024-01-01T12:00:00"
}
See examples/ directory for sample messages.
Environment Configuration
Copy .env.example to .env and configure:
# Application
APP_NAME=application-skeleton
APP_ENV=local
# RabbitMQ
RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASS=guest
RABBITMQ_VHOST=/
RABBITMQ_TIMEOUT=10
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_DB=0
Development
Makefile Commands
make build # Build Docker images
make up # Start services (builds if needed)
make down # Stop services
make restart # Restart services
make logs # View application logs
make stop # Stop and remove containers
make clean # Remove all containers and volumes
Type Checking
pyright
Code Style
Follow PEP 8 conventions. Configuration in .editorconfig.
Key Design Principles
- Kernel is Core: Infrastructure lives in
kernel/, never import fromfeature/intokernel/ - Shared for Common Code: Cross-cutting concerns and shared implementations belong in
shared/ - Features are Independent: Features should not depend on other features directly
- DI Over Coupling: Use the DI container to manage dependencies
- Async-First: Embrace async/await throughout the codebase
- Events for Communication: Features communicate through events, not direct calls
- Provider Pattern: Each feature must implement the Provider pattern with
register()andbootstrap()methods
Performance Considerations
- Graceful Shutdown: Application responds to SIGTERM and SIGINT signals
- Resource Cleanup: Set
stop_grace_period: 3sin Docker Compose for clean shutdown - Connection Pooling: RabbitMQ and Redis connections are pooled for efficiency
- Event-Driven: Non-blocking event handling allows processing many messages concurrently
Troubleshooting
Application doesn't stop gracefully
Ensure stop_grace_period is set in docker-compose.yaml:
services:
application:
stop_grace_period: 3s
Messages not being processed
- Check RabbitMQ is healthy:
docker ps - Verify queue and routing key match the listeners
- View logs:
make logs - Check environment variables in
.env
Type checking errors
Ensure typings are installed:
ls typings/
# Should contain stubs for aioredis, aioredlock, punq
ConnectionError to RabbitMQ
- Verify
depends_onconditions in compose.yaml:depends_on: rabbitmq: condition: service_healthy - Check healthcheck passes:
docker ps - Verify credentials in
.env
Dependencies
Core dependencies include:
punq- Dependency injection containeraio-pika- RabbitMQ clientredis- Redis clientaioredlock- Distributed lockingpyee- Event emitter
See requirements.txt for complete list and versions.
Contributing
- Follow the feature-oriented structure
- Each feature should be self-contained
- Use dependency injection for all dependencies
- Add type annotations to all functions
- Write docstrings for public APIs
- Test features in isolation using the DI container
For more information on feature-oriented architecture and event-driven systems, see the examples in the entry directory.