Part 1: Project Setup & StructureΒΆ
Time: 30 minutes | Prerequisites: Python 3.11+, Poetry
Welcome to the Mario's Pizzeria tutorial series! In this first part, you'll set up your development environment and understand the project structure that supports clean architecture principles.
π― What You'll LearnΒΆ
By the end of this tutorial, you'll understand:
- How to structure a Neuroglia application using clean architecture layers
- The role of the
WebApplicationBuilderin bootstrapping applications - How dependency injection works in the framework
- How to configure multiple sub-applications (API + UI)
π¦ Project SetupΒΆ
1. Install DependenciesΒΆ
First, let's install the framework and required packages:
# Create a new project directory
mkdir mario-pizzeria
cd mario-pizzeria
# Initialize poetry project
poetry init -n
# Add Neuroglia framework
poetry add neuroglia
# Add additional dependencies
poetry add fastapi uvicorn motor pymongo
poetry add python-multipart jinja2 # For UI support
poetry add starlette # For session middleware
# Install all dependencies
poetry install
2. Create Directory StructureΒΆ
Neuroglia enforces clean architecture with strict layer separation. Create this structure:
mario-pizzeria/
βββ main.py # Application entry point
βββ api/ # π API Layer (REST controllers, DTOs)
β βββ __init__.py
β βββ controllers/
β β βββ __init__.py
β βββ dtos/
β βββ __init__.py
βββ application/ # πΌ Application Layer (Commands, Queries, Handlers)
β βββ __init__.py
β βββ commands/
β β βββ __init__.py
β βββ queries/
β β βββ __init__.py
β βββ events/
β β βββ __init__.py
β βββ services/
β βββ __init__.py
βββ domain/ # ποΈ Domain Layer (Entities, Business Rules)
β βββ __init__.py
β βββ entities/
β β βββ __init__.py
β βββ repositories/
β βββ __init__.py
βββ integration/ # π Integration Layer (Database, External APIs)
β βββ __init__.py
β βββ repositories/
β βββ __init__.py
βββ ui/ # Web UI (templates, static files)
βββ controllers/
β βββ __init__.py
βββ templates/
Why this structure?
The Dependency Rule states that dependencies only point inward:
- Domain: Pure business logic, no external dependencies
- Application: Orchestrates domain logic, defines use cases
- Integration: Implements technical concerns (database, HTTP clients)
- API: Exposes functionality via REST endpoints
This separation makes code testable, maintainable, and replaceable.
π Creating the Application Entry PointΒΆ
Let's create main.py - the heart of your application:
#!/usr/bin/env python3
"""
Mario's Pizzeria - A Clean Architecture Sample Application
"""
import logging
from neuroglia.hosting.web import WebApplicationBuilder, SubAppConfig
from neuroglia.mediation import Mediator
from neuroglia.mapping import Mapper
from neuroglia.serialization.json import JsonSerializer
# Configure logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
def create_pizzeria_app():
"""
Create Mario's Pizzeria application using WebApplicationBuilder.
This demonstrates the framework's opinionated approach to application
bootstrapping with dependency injection and modular configuration.
"""
# 1οΈβ£ Create the application builder
builder = WebApplicationBuilder()
# 2οΈβ£ Configure core framework services
# Mediator: Handles commands and queries (CQRS pattern)
Mediator.configure(
builder,
[
"application.commands", # Command handlers
"application.queries", # Query handlers
"application.events" # Event handlers
]
)
# Mapper: Object-to-object transformations (entities β DTOs)
Mapper.configure(
builder,
[
"application.mapping",
"api.dtos",
"domain.entities"
]
)
# JsonSerializer: Type-aware JSON serialization
JsonSerializer.configure(
builder,
[
"domain.entities"
]
)
# 3οΈβ£ Configure sub-applications
# API sub-app: REST API with JSON responses
builder.add_sub_app(
SubAppConfig(
path="/api", # Mounted at /api prefix
name="api",
title="Mario's Pizzeria API",
description="Pizza ordering REST API",
version="1.0.0",
controllers=["api.controllers"], # Auto-discover controllers
docs_url="/docs", # OpenAPI docs at /api/docs
)
)
# UI sub-app: Web interface with templates
builder.add_sub_app(
SubAppConfig(
path="/", # Mounted at root
name="ui",
title="Mario's Pizzeria UI",
description="Pizza ordering web interface",
version="1.0.0",
controllers=["ui.controllers"],
static_files={"/static": "static"}, # Serve static assets
templates_dir="ui/templates", # Jinja2 templates
docs_url=None, # No API docs for UI
)
)
# 4οΈβ£ Build the complete application
app = builder.build_app_with_lifespan(
title="Mario's Pizzeria",
description="Complete pizza ordering system",
version="1.0.0",
debug=True
)
log.info("π Mario's Pizzeria is ready!")
return app
def main():
"""Entry point for running the application"""
import uvicorn
print("π Starting Mario's Pizzeria on http://localhost:8080")
print("π API Documentation: http://localhost:8080/api/docs")
print("π UI: http://localhost:8080/")
# Run with hot reload for development
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8080,
reload=True,
log_level="info"
)
# Create app instance for uvicorn
app = create_pizzeria_app()
if __name__ == "__main__":
main()
π Understanding the CodeΒΆ
WebApplicationBuilderΒΆ
The WebApplicationBuilder is the central bootstrapping mechanism in Neuroglia:
It provides:
- Service Container: Dependency injection with scoped, singleton, transient lifetimes
- Configuration: Automatic scanning and registration
- Sub-App Support: Multiple FastAPI apps mounted to a main host
- Lifespan Management: Startup/shutdown hooks for resources
Mediator PatternΒΆ
The Mediator decouples request handling from execution:
Why use mediator?
β Without Mediator (tight coupling):
# Controller directly calls service
class PizzaController:
def __init__(self, pizza_service: PizzaService):
self.service = pizza_service
def create_pizza(self, data):
return self.service.create_pizza(data) # Direct dependency
β With Mediator (loose coupling):
# Controller sends command to mediator
class PizzaController:
def __init__(self, mediator: Mediator):
self.mediator = mediator
async def create_pizza(self, data):
command = CreatePizzaCommand(name=data.name)
return await self.mediator.execute_async(command) # Mediator routes it
The mediator automatically finds and executes the right handler. Controllers stay thin and testable.
Sub-Application ArchitectureΒΆ
Neuroglia supports multiple FastAPI apps mounted to a main host:
builder.add_sub_app(SubAppConfig(path="/api", ...)) # API at /api
builder.add_sub_app(SubAppConfig(path="/", ...)) # UI at /
Benefits:
- Separation of Concerns: API logic separate from UI logic
- Different Authentication: JWT for API, sessions for UI
- Independent Documentation: OpenAPI docs only for API
- Static File Handling: Serve assets efficiently for UI
This is Mario's Pizzeria's actual architecture.
π§ͺ Test Your SetupΒΆ
Run the application:
You should see:
π Starting Mario's Pizzeria on http://localhost:8080
π API Documentation: http://localhost:8080/api/docs
π UI: http://localhost:8080/
INFO: Started server process
INFO: Waiting for application startup.
π Mario's Pizzeria is ready!
INFO: Application startup complete.
Visit http://localhost:8080/api/docs - you'll see the OpenAPI documentation (empty for now).
π Key TakeawaysΒΆ
- Clean Architecture Layers: Domain β Application β API/Integration
- WebApplicationBuilder: Central bootstrapping with DI container
- Mediator Pattern: Decouples controllers from business logic
- Sub-Applications: Multiple FastAPI apps with different concerns
- Auto-Discovery: Framework automatically finds and registers controllers/handlers
π What's Next?ΒΆ
In Part 2: Domain Model, you'll learn:
- How to create domain entities with business rules
- The difference between
EntityandAggregateRoot - Domain events and why they matter
- Value objects for type safety
π‘ Common IssuesΒΆ
ImportError: No module named 'neuroglia'
Port 8080 already in use
Module not found when running
Next: Part 2: Domain Model β