Skip to content

πŸ• Neuroglia Python FrameworkΒΆ

Neuroglia is a lightweight, opinionated framework built on FastAPI that enforces clean architecture principles and provides comprehensive tooling for building maintainable microservices. Through practical examples with Mario's Pizzeria, we'll show you how to build production-ready applications using CQRS, dependency injection, and event-driven architecture.

🎯 What You'll Build: Mario's Pizzeria Management System¢

Throughout the documentation, we use Mario's Pizzeria as a unified example to demonstrate every framework feature:

  • πŸ“‹ Order Management: Place orders, track status, handle payments
  • πŸ• Menu Administration: Manage pizzas, ingredients, pricing
  • πŸ‘¨β€πŸ³ Kitchen Operations: Workflow management, status updates, notifications
  • πŸ“Š Analytics: Order tracking, sales reporting, customer insights
  • πŸ” Authentication: OAuth-based access control for staff and customers

This real-world example shows how all framework components work together to create a complete business application.

✨ What Makes Neuroglia Special?¢

  • πŸ—οΈ Clean Architecture Enforced: Clear separation between API, Application, Domain, and Integration layers
  • πŸ’‰ Powerful Dependency Injection: Lightweight container with automatic service discovery and lifetime management
  • 🎯 CQRS & Mediation Built-in: Command Query Responsibility Segregation with comprehensive mediator pattern
  • πŸ“‘ Event-Driven by Design: Native CloudEvents support, domain events, and reactive programming
  • πŸ”Œ MVC Done Right: Class-based controllers with automatic discovery and OpenAPI generation
  • πŸ—„οΈ Flexible Data Access: Repository pattern with file-based, MongoDB, and event sourcing support
  • πŸ“Š Smart Object Mapping: Bidirectional mapping between domain models and DTOs
  • πŸ§ͺ Testing First: Built-in testing utilities and patterns for all architectural layers

πŸš€ Quick Start: Your First Pizzeria AppΒΆ

Get Mario's Pizzeria running in minutes:

# Install Neuroglia
pip install neuroglia

# Create a simple pizzeria API
# main.py
from dataclasses import dataclass
from typing import List
from neuroglia.hosting.web import WebApplicationBuilder
from neuroglia.mvc import ControllerBase
from classy_fastapi import get, post

@dataclass
class PizzaDto:
    name: str
    size: str
    price: float

@dataclass
class OrderDto:
    id: str
    customer_name: str
    pizzas: List[PizzaDto]
    total: float
    status: str

class MenuController(ControllerBase):
    """Mario's menu management"""

    @get("/menu/pizzas", response_model=List[PizzaDto])
    async def get_pizzas(self) -> List[PizzaDto]:
        return [
            PizzaDto("Margherita", "large", 15.99),
            PizzaDto("Pepperoni", "large", 17.99),
            PizzaDto("Quattro Stagioni", "large", 19.99)
        ]

class OrdersController(ControllerBase):
    """Mario's order management"""

    @post("/orders", response_model=OrderDto)
    async def place_order(self, customer_name: str, pizza_names: List[str]) -> OrderDto:
        # Simple order creation
        pizzas = [PizzaDto(name, "large", 15.99) for name in pizza_names]
        total = sum(p.price for p in pizzas)

        return OrderDto(
            id="order_123",
            customer_name=customer_name,
            pizzas=pizzas,
            total=total,
            status="received"
        )

def create_pizzeria_app():
    """Create Mario's Pizzeria application"""
    builder = WebApplicationBuilder()

    # Add controllers
    builder.services.add_controllers(["__main__"])

    app = builder.build()
    app.use_controllers()

    return app

if __name__ == "__main__":
    app = create_pizzeria_app()
    app.run()
# Run the pizzeria
python main.py

# Test the API
curl http://localhost:8000/menu/pizzas
curl -X POST "http://localhost:8000/orders?customer_name=Mario" \
     -H "Content-Type: application/json" \
     -d '["Margherita", "Pepperoni"]'

πŸ‘‰ Complete Tutorial: Build the Full Pizzeria System

�️ Architecture Overview¢

Mario's Pizzeria demonstrates clean, layered architecture:

    🌐 API Layer (Controllers & DTOs)
         OrdersController, MenuController, KitchenController
         ↓ Commands & Queries
    πŸ’Ό Application Layer (CQRS Handlers)
         PlaceOrderHandler, GetMenuHandler, UpdateOrderStatusHandler
         ↓ Domain Operations  
    πŸ›οΈ Domain Layer (Business Logic)
         Order, Pizza, Customer entities with business rules
         ↑ Repository Interfaces
    πŸ”Œ Integration Layer (Data & External Services)
         FileOrderRepository, MongoOrderRepository, PaymentService

Each layer has specific responsibilities:

  • 🌐 API Layer: HTTP endpoints, request/response handling, authentication
  • πŸ’Ό Application Layer: Business workflows, command/query processing, event coordination
  • πŸ›οΈ Domain Layer: Core business rules, entity logic, domain events
  • πŸ”Œ Integration Layer: Data persistence, external APIs, infrastructure services

πŸ“– Deep Dive: Clean Architecture with Mario's Pizzeria

πŸŽͺ Core FeaturesΒΆ

πŸ’‰ Dependency InjectionΒΆ

Powerful service container demonstrated through Mario's Pizzeria:

# Service registration for pizzeria
builder.services.add_scoped(IOrderRepository, FileOrderRepository)
builder.services.add_scoped(IPaymentService, MockPaymentService) 
builder.services.add_mediator()
builder.services.add_controllers(["api.controllers"])

# Constructor injection in controllers
class OrdersController(ControllerBase):
    def __init__(self, service_provider: ServiceProviderBase, 
                 mapper: Mapper, mediator: Mediator):
        super().__init__(service_provider, mapper, mediator)

    @post("/orders", response_model=OrderDto)
    async def place_order(self, request: PlaceOrderDto) -> OrderDto:
        command = self.mapper.map(request, PlaceOrderCommand)
        result = await self.mediator.execute_async(command)
        return self.process(result)

πŸ“– Dependency Injection with Mario's Pizzeria

🎯 CQRS & Mediation¢

Clean command/query separation demonstrated through pizza ordering:

# Command for placing orders
@dataclass
class PlaceOrderCommand(Command[OperationResult[OrderDto]]):
    customer_name: str
    customer_phone: str
    customer_address: str
    pizzas: List[PizzaOrderDto]
    payment_method: str

# Handler with business logic
class PlaceOrderCommandHandler(CommandHandler[PlaceOrderCommand, OperationResult[OrderDto]]):
    def __init__(self, order_repository: IOrderRepository, 
                 payment_service: IPaymentService,
                 mapper: Mapper):
        self.order_repository = order_repository
        self.payment_service = payment_service
        self.mapper = mapper

    async def handle_async(self, command: PlaceOrderCommand) -> OperationResult[OrderDto]:
        # 1. Create domain entity with business logic
        pizzas = [Pizza(p.name, p.size, p.extras) for p in command.pizzas]
        order = Order.create_new(
            command.customer_name, 
            command.customer_phone,
            command.customer_address,
            pizzas,
            command.payment_method
        )

        # 2. Process payment
        payment_result = await self.payment_service.process_payment_async(
            order.total_amount, command.payment_method
        )
        if not payment_result.is_success:
            return self.bad_request("Payment processing failed")

        # 3. Save order and return result
        saved_order = await self.order_repository.save_async(order)
        order_dto = self.mapper.map(saved_order, OrderDto)
        return self.created(order_dto)

# Query for retrieving menu
@dataclass  
class GetMenuQuery(Query[List[PizzaDto]]):
    category: Optional[str] = None

class GetMenuQueryHandler(QueryHandler[GetMenuQuery, List[PizzaDto]]):
    async def handle_async(self, query: GetMenuQuery) -> List[PizzaDto]:
        # Query logic here
        pizzas = await self.pizza_repository.get_all_async()
        if query.category:
            pizzas = [p for p in pizzas if p.category == query.category]
        return [self.mapper.map(p, PizzaDto) for p in pizzas]

# Usage in controller
@post("/orders", response_model=OrderDto)
async def place_order(self, request: PlaceOrderDto) -> OrderDto:
    command = self.mapper.map(request, PlaceOrderCommand)
    result = await self.mediator.execute_async(command)
    return self.process(result)

πŸ“– CQRS & Mediation with Mario's Pizzeria

πŸ”Œ MVC ControllersΒΆ

Class-based controllers with automatic discovery demonstrated through pizzeria APIs:

class OrdersController(ControllerBase):
    """Mario's order management endpoint"""

    @post("/orders", response_model=OrderDto, status_code=201)
    async def place_order(self, request: PlaceOrderDto) -> OrderDto:
        """Place a new pizza order"""
        command = self.mapper.map(request, PlaceOrderCommand)
        result = await self.mediator.execute_async(command)
        return self.process(result)

    @get("/orders/{order_id}", response_model=OrderDto)
    async def get_order(self, order_id: str) -> OrderDto:
        """Get order details by ID"""
        query = GetOrderByIdQuery(order_id=order_id)
        result = await self.mediator.execute_async(query)
        return self.process(result)

class MenuController(ControllerBase):
    """Mario's menu management"""

    @get("/menu/pizzas", response_model=List[PizzaDto])
    async def get_pizzas(self, category: Optional[str] = None) -> List[PizzaDto]:
        """Get available pizzas, optionally filtered by category"""
        query = GetMenuQuery(category=category)
        result = await self.mediator.execute_async(query)
        return self.process(result)

# Automatic OpenAPI documentation generation
# Built-in validation, error handling, and response formatting

πŸ“– MVC Controllers with Mario's Pizzeria

πŸ“‘ Event-Driven ArchitectureΒΆ

Native support for domain events and reactive programming demonstrated through pizzeria operations:

# Domain events in Mario's Pizzeria
@dataclass
class OrderPlacedEvent(DomainEvent):
    order_id: str
    customer_name: str
    total_amount: Decimal
    pizzas: List[str]

@dataclass
class OrderReadyEvent(DomainEvent):
    order_id: str
    customer_phone: str
    pickup_time: datetime

# Event handlers for pizzeria workflow
class KitchenNotificationHandler(EventHandler[OrderPlacedEvent]):
    """Notify kitchen when order is placed"""
    async def handle_async(self, event: OrderPlacedEvent):
        await self.kitchen_service.add_to_queue(event.order_id, event.pizzas)

class CustomerNotificationHandler(EventHandler[OrderReadyEvent]):
    """Notify customer when order is ready"""
    async def handle_async(self, event: OrderReadyEvent):
        message = f"Your order {event.order_id} is ready for pickup!"
        await self.sms_service.send_message(event.customer_phone, message)

# Events are automatically published when domain entities change
class Order(Entity):
    def update_status(self, new_status: OrderStatus, updated_by: str):
        self.status = new_status
        self.last_updated = datetime.utcnow()
        self.updated_by = updated_by

        # Raise domain event
        if new_status == OrderStatus.READY:
            self.raise_event(OrderReadyEvent(
                order_id=self.id,
                customer_phone=self.customer_phone,
                pickup_time=datetime.utcnow()
            ))

πŸ“– Event-Driven Architecture with Mario's Pizzeria

πŸ—„οΈ Data AccessΒΆ

Flexible repository pattern with multiple storage backends demonstrated through Mario's Pizzeria:

# Repository interface for orders
class IOrderRepository(Repository[Order, str]):
    async def get_by_customer_phone_async(self, phone: str) -> List[Order]:
        pass

    async def get_orders_by_status_async(self, status: OrderStatus) -> List[Order]:
        pass

# File-based implementation (development)
class FileOrderRepository(IOrderRepository):
    def __init__(self, data_directory: str):
        self.data_directory = Path(data_directory)
        self.data_directory.mkdir(exist_ok=True)

    async def save_async(self, order: Order) -> Order:
        order_data = {
            "id": order.id,
            "customer_name": order.customer_name,
            "status": order.status.value,
            "total_amount": float(order.total_amount),
            "created_at": order.created_at.isoformat()
        }

        file_path = self.data_directory / f"{order.id}.json"
        with open(file_path, 'w') as f:
            json.dump(order_data, f, indent=2)

        return order

# MongoDB implementation (production)
class MongoOrderRepository(IOrderRepository):
    def __init__(self, collection: Collection):
        self.collection = collection

    async def save_async(self, order: Order) -> Order:
        document = self._order_to_document(order)
        await self.collection.insert_one(document)
        return order

    async def get_orders_by_status_async(self, status: OrderStatus) -> List[Order]:
        cursor = self.collection.find({"status": status.value})
        documents = await cursor.to_list(length=None)
        return [self._document_to_order(doc) for doc in documents]

# Event sourcing for complex workflows
class EventSourcedOrderRepository(IOrderRepository):
    """Track complete order lifecycle through events"""
    async def save_async(self, order: Order) -> Order:
        # Persist domain events instead of just final state
        events = order.get_uncommitted_events()
        for event in events:
            await self.event_store.append_async(order.id, event)
        return order

πŸ“– Data Access with Mario's Pizzeria

πŸ“Š Object MappingΒΆ

Bidirectional mapping between domain entities and DTOs:

# Automatic mapping configuration
builder.services.add_auto_mapper()

# Domain entity
class Order(Entity):
    customer_name: str
    pizzas: List[Pizza] 
    total_amount: Decimal
    status: OrderStatus

# DTO for API
@dataclass
class OrderDto:
    id: str
    customer_name: str
    pizzas: List[PizzaDto]
    total_amount: float
    status: str
    created_at: str

# Usage in handlers
class PlaceOrderCommandHandler(CommandHandler[PlaceOrderCommand, OperationResult[OrderDto]]):
    async def handle_async(self, command: PlaceOrderCommand) -> OperationResult[OrderDto]:
        # Create domain entity
        order = Order.create_new(command.customer_name, ...)

        # Save entity
        saved_order = await self.order_repository.save_async(order)

        # Map to DTO for response
        order_dto = self.mapper.map(saved_order, OrderDto)
        return self.created(order_dto)

πŸŽ“ Learn Through Mario's PizzeriaΒΆ

All documentation uses Mario's Pizzeria as a consistent, comprehensive example:

πŸ• Complete Business DomainΒΆ

  • πŸ‘₯ Customer Management: Registration, authentication, order history
  • πŸ“‹ Order Processing: Place orders, payment processing, status tracking
  • πŸ• Menu Management: Pizzas, ingredients, pricing, categories
  • πŸ‘¨β€πŸ³ Kitchen Operations: Order queue, preparation workflow, notifications
  • πŸ“Š Business Analytics: Sales reports, popular items, customer insights
  • πŸ” Staff Authentication: Role-based access for different staff functions

πŸ—οΈ Architecture DemonstratedΒΆ

Each major framework feature is shown through realistic pizzeria scenarios:

  • 🌐 API Layer: RESTful endpoints for customers and staff
  • οΏ½ Application Layer: Business workflows like order processing
  • πŸ›οΈ Domain Layer: Rich business entities with validation and events
  • πŸ”Œ Integration Layer: File storage, MongoDB, payment services, SMS notifications

🎯 Progressive Learning Path¢

  1. πŸš€ Getting Started - Build your first pizzeria app step-by-step
  2. πŸ—οΈ Architecture - Understand clean architecture through pizzeria layers
  3. οΏ½ Dependency Injection - Configure pizzeria services and repositories
  4. 🎯 CQRS & Mediation - Implement order processing workflows
  5. οΏ½ MVC Controllers - Build pizzeria API endpoints
  6. πŸ—„οΈ Data Access - Persist orders with file and database storage

πŸ“š DocumentationΒΆ

πŸš€ Getting StartedΒΆ

πŸŽͺ Feature GuidesΒΆ

Feature Mario's Pizzeria Example Documentation
πŸ—οΈ Architecture Complete pizzeria system layers πŸ“– Guide
πŸ’‰ Dependency Injection Service registration for pizzeria πŸ“– Guide
🎯 CQRS & Mediation Order processing commands & queries πŸ“– Guide
πŸ”Œ MVC Controllers Pizzeria API endpoints πŸ“– Guide
πŸ—„οΈ Data Access Order & menu repositories πŸ“– Guide

πŸ› οΈ Installation & RequirementsΒΆ

PrerequisitesΒΆ

  • Python 3.8+ (Python 3.11+ recommended)
  • pip or poetry for dependency management

InstallationΒΆ

# Install from PyPI (coming soon)
pip install neuroglia

# Or install from source for development
git clone https://github.com/neuroglia-io/python.git
cd python
pip install -e .

Optional DependenciesΒΆ

# MongoDB support
pip install neuroglia[mongo]

# Event sourcing with EventStoreDB
pip install neuroglia[eventstore]

# All features
pip install neuroglia[all]

🀝 Community & Support¢

Getting HelpΒΆ

  • πŸ“– Documentation: Comprehensive guides with Mario's Pizzeria examples
  • πŸ’¬ GitHub Discussions: Ask questions and share ideas
  • πŸ› Issues: Report bugs and request features
  • πŸ“§ Email: Contact maintainers directly

ContributingΒΆ

We welcome contributions from the community:

  • πŸ“ Documentation - Help improve pizzeria examples and guides
  • πŸ› Bug Reports - Help us identify and fix issues
  • ✨ Features - Propose new framework capabilities
  • πŸ§ͺ Tests - Improve test coverage and quality
  • πŸ”§ Code - Submit PRs with improvements and fixes

RoadmapΒΆ

Upcoming features and improvements:

  • Enhanced Resource Oriented Architecture - Extended watcher and controller patterns
  • Advanced Event Sourcing - More sophisticated event store integrations
  • Performance Optimizations - Faster startup and runtime performance
  • Additional Storage Backends - PostgreSQL, Redis, and more
  • Extended Authentication - Additional OAuth providers and JWT enhancements
  • Monitoring & Observability - Built-in metrics and distributed tracing

οΏ½ LicenseΒΆ

This project is licensed under the MIT License - see the LICENSE file for details.

🌟 Why Choose Neuroglia?¢

βœ… Production Ready: Battle-tested patterns used in real-world applications
βœ… Developer Friendly: Intuitive APIs with consistent Mario's Pizzeria examples
βœ… Highly Testable: Comprehensive testing utilities and patterns built-in
βœ… Scalable Architecture: Clean architecture principles that grow with your needs
βœ… Modern Framework: Leverages latest Python 3.11+ and FastAPI features
βœ… Flexible Design: Use individual components or the complete framework
βœ… Excellent Documentation: Every feature explained through practical examples
βœ… Active Development: Continuously improved with community feedback


Ready to build Mario's Pizzeria? Get Started Now πŸ• | Data Access | Repository pattern and persistence | πŸ“– Guide | | Event Handling | Events, messaging, and reactive programming | πŸ“– Guide | | Object Mapping | Automatic object-to-object mapping | πŸ“– Guide | | Configuration | Settings and environment management | πŸ“– Guide | | Hosting | Web application hosting and lifecycle | πŸ“– Guide |

πŸ“‹ RequirementsΒΆ

  • Python 3.11+
  • FastAPI (automatic)
  • Pydantic (automatic)
  • Optional: MongoDB, EventStoreDB, Redis (based on features used)

🀝 Contributing¢

We welcome contributions! Here's how you can help:

  • πŸ› Report bugs - Found an issue? Let us know!
  • πŸ’‘ Suggest features - Have an idea? We'd love to hear it!
  • πŸ“ Improve docs - Help make our documentation better
  • πŸ”§ Submit PRs - Code contributions are always welcome

πŸ‘‰ Contributing Guide

πŸ“„ LicenseΒΆ

Running Background TasksΒΆ

Neuroglia integrates with apscheduler for background tasks:

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from neuroglia.hosting.abstractions import HostedService

class BackgroundTaskService(HostedService):
    def __init__(self):
        self._scheduler = AsyncIOScheduler()

    async def start_async(self):
        # Add jobs
        self._scheduler.add_job(self._process_data, 'interval', minutes=5)
        self._scheduler.start()

    async def stop_async(self):
        self._scheduler.shutdown()

    async def _process_data(self):
        # Task implementation
        pass

Advanced FeaturesΒΆ

Real-time Communication with CloudEventsΒΆ

from neuroglia.eventing.cloud_events.infrastructure import CloudEventIngestor
from neuroglia.eventing.cloud_events.decorators import cloud_event_handler

class NotificationService:
    def __init__(self, event_ingestor: CloudEventIngestor):
        event_ingestor.subscribe("user.created", self._on_user_created)

    @cloud_event_handler
    async def _on_user_created(self, event_data):
        # Process user created event
        user_id = event_data["id"]
        # Send notification

Custom Repository ImplementationΒΆ

from neuroglia.data.infrastructure.abstractions import Repository

class CustomRepository(Repository[Entity, str]):
    async def add(self, entity: Entity) -> None:
        # Custom implementation

    async def update(self, entity: Entity) -> None:
        # Custom implementation

    async def remove(self, entity: Entity) -> None:
        # Custom implementation

    async def find_by_id(self, id: str) -> Optional[Entity]:
        # Custom implementation

SamplesΒΆ

OpenBankΒΆ

Implements a simplified Bank that manages Accounts, Users and Transactions with full Event Sourcing, CQRS

Explore OpenBank

Desktop ControllerΒΆ

Remotely and securely control custom files or commands on a Desktop running the app as a Docker container...

Explore Desktop Controller

API GatewayΒΆ

Expose single entry point for 3rd party clients into an internal layer, like a GenAI stack... Models a Prompt entity, enforces a business logic (e.g. Prompt' state-machine), handles scheduled background task (with persistence), exposes API with multiple Security schemes, ...

Explore API Gateway

Cisco Remote Output CollectorΒΆ

Statefull microservice that handles complex and custom HTTP Commands which in turn each encapsulates arbitrary interactions with given Cisco Device(s) via Telnet, such as FindPrompt, CollectCommandLineOutput, AddConfiguration, SaveConfiguration, Ping, Traceroute, ClearNatTranslation, CheckReachability, BounceInterface, RunViaTelnetTo, FindSpanningTreeRoot, ... etc.

Explore IOS ROC

Current state: functional but simple implemention, 100% stateless collection of multiple CLI to a single device via Telnet.

TODO:

  • [ ] Add Session management (defines a Pod for subsequent scenarios) with persistence
  • [ ] Add DeviceConnection and ConnectionManager
  • [ ] Add DeviceDrivers and PromptPatterns libraries
  • [ ] ...

DeploymentΒΆ

Docker DeploymentΒΆ

The framework is designed to work seamlessly with Docker. A typical Dockerfile might look like:

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]

Environment ConfigurationΒΆ

Following the 12-Factor App principles, configuration is stored in environment variables:

from neuroglia.hosting.abstractions import ApplicationSettings
from pydantic import BaseSettings

class MyAppSettings(ApplicationSettings):
    database_url: str
    api_key: str
    debug_mode: bool = False

TestingΒΆ

The framework supports comprehensive testing with pytest:

# Example test for a command handler
async def test_create_user_command():
    # Arrange
    handler = CreateUserCommandHandler(mock_repository)
    command = CreateUserCommand("test", "test@example.com")

    # Act
    result = await handler.handle(command)

    # Assert
    assert result is not None
    assert mock_repository.add.called_once

Best PracticesΒΆ

  1. Keep Domain Models Pure: Domain models should be free of infrastructure concerns
  2. Use Commands for State Changes: All state-changing operations should be modeled as commands
  3. Use Queries for Reading Data: All data retrieval should be modeled as queries
  4. Leverage Dependency Injection: Always use DI to create loosely coupled components
  5. Handle Errors with Problem Details: Use the standard ProblemDetails format for error responses
  6. Follow Layered Architecture: Maintain clear boundaries between API, Application, Domain, and Integration layers

ConclusionΒΆ

The Neuroglia Python Framework provides a comprehensive foundation for building clean, maintainable, and feature-rich microservices. By embracing modern architectural patterns like CQRS, Event Sourcing, and Clean Architecture, it helps developers create applications that are easier to understand, test, and evolve over time.

For more information, check out the sample applications or contribute to the framework development.