Skip to content

🏷️ Source Code Naming Conventions¢

Consistent naming conventions are crucial for maintainable, readable, and professional codebases. The Neuroglia framework follows Python's established conventions while adding domain-specific patterns for clean architecture. This reference provides comprehensive guidelines for naming across all layers of your application.

🎯 What You'll Learn¢

  • When to use snake_case vs CamelCase vs PascalCase across different contexts
  • Naming patterns for entities, events, handlers, controllers, and methods
  • Layer-specific conventions that enforce clean architecture boundaries
  • Benefits of consistent naming conventions for team productivity
  • Mario's Pizzeria examples demonstrating proper naming in practice

πŸ“‹ Benefits of Naming ConventionsΒΆ

🧠 Cognitive Load Reduction¢

Consistent patterns reduce mental overhead when reading code. Developers can instantly identify the purpose and layer of any component based on its name.

πŸ‘₯ Team CollaborationΒΆ

Standardized naming eliminates debates about "what to call this" and ensures new team members can navigate the codebase intuitively.

πŸ” Searchability & NavigationΒΆ

Well-named components are easier to find using IDE search, grep, and other tools. Consistent patterns enable powerful refactoring operations.

πŸ—οΈ Architecture EnforcementΒΆ

Naming conventions reinforce clean architecture boundaries - you can immediately tell if a component violates layer dependencies.

πŸš€ Productivity & MaintenanceΒΆ

Less time spent deciphering unclear names means more time focused on business logic and feature development.


🐍 Python Language Conventions¢

The framework strictly follows PEP 8 and Python naming conventions as the foundation:

snake_case UsageΒΆ

Files and Modules:

# βœ… Correct
user_service.py
create_order_command.py
bank_account_repository.py

# ❌ Incorrect
UserService.py
CreateOrderCommand.py
BankAccount-Repository.py

Variables and Functions:

# βœ… Correct
user_name = "Mario"
total_amount = calculate_order_total()
order_placed_at = datetime.now()

def process_payment_async(amount: Decimal) -> bool:
    pass

# ❌ Incorrect
userName = "Mario"
totalAmount = calculateOrderTotal()
OrderPlacedAt = datetime.now()

def ProcessPaymentAsync(amount: Decimal) -> bool:
    pass

Method and Attribute Names:

class Pizza:
    def __init__(self):
        self.pizza_name = ""        # snake_case attributes
        self.base_price = Decimal("0")
        self.available_sizes = []

    def calculate_total_price(self):  # snake_case methods
        pass

    async def save_to_database_async(self):  # async suffix
        pass

PascalCase UsageΒΆ

Classes, Types, and Interfaces:

# βœ… Correct - Classes
class OrderService:
    pass

class CreatePizzaCommand:
    pass

class PizzaOrderHandler:
    pass

# βœ… Correct - Type Variables
TEntity = TypeVar("TEntity")
TKey = TypeVar("TKey")
TResult = TypeVar("TResult")

# βœ… Correct - Exceptions
class ValidationException(Exception):
    pass

class OrderNotFoundException(Exception):
    pass

Enums:

class OrderStatus(Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    PREPARING = "preparing"
    READY = "ready"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

UPPER_CASE UsageΒΆ

Constants:

# βœ… Correct - Module-level constants
DEFAULT_PIZZA_SIZE = "medium"
MAX_ORDER_ITEMS = 20
API_BASE_URL = "https://api.mariospizza.com"

# βœ… Correct - Class constants
class PizzaService:
    DEFAULT_COOKING_TIME = 15  # minutes
    MAX_TOPPINGS_PER_PIZZA = 8

πŸ—οΈ Layer-Specific Naming ConventionsΒΆ

The framework enforces different naming patterns for each architectural layer to maintain clean separation of concerns.

🌐 API Layer (api/)¢

The API layer handles HTTP requests and responses, following REST conventions.

Controllers:

# Pattern: {Entity}Controller (PascalCase)
class PizzasController(ControllerBase):
    pass

class OrdersController(ControllerBase):
    pass

class CustomersController(ControllerBase):
    pass

# ❌ Avoid
class PizzaController:      # Singular form
class Pizza_Controller:     # snake_case
class pizzaController:      # camelCase

Controller Methods:

class PizzasController(ControllerBase):
    # Pattern: HTTP verb + descriptive name (snake_case)
    @get("/{pizza_id}")
    async def get_pizza(self, pizza_id: str) -> PizzaDto:
        pass

    @post("/")
    async def create_pizza(self, pizza_dto: CreatePizzaDto) -> PizzaDto:
        pass

    @put("/{pizza_id}")
    async def update_pizza(self, pizza_id: str, pizza_dto: UpdatePizzaDto) -> PizzaDto:
        pass

    @delete("/{pizza_id}")
    async def delete_pizza(self, pizza_id: str) -> None:
        pass

    # Complex operations get descriptive names
    @post("/{pizza_id}/customize")
    async def customize_pizza_toppings(self, pizza_id: str, toppings: List[str]) -> PizzaDto:
        pass

DTOs (Data Transfer Objects):

# Pattern: {Purpose}{Entity}Dto (PascalCase)
@dataclass
class PizzaDto:
    pizza_id: str           # snake_case fields
    pizza_name: str
    base_price: Decimal
    available_sizes: List[str]

@dataclass
class CreatePizzaDto:
    pizza_name: str
    base_price: Decimal
    ingredient_ids: List[str]

@dataclass
class UpdatePizzaDto:
    pizza_name: Optional[str] = None
    base_price: Optional[Decimal] = None

# Specialized DTOs
@dataclass
class PizzaMenuItemDto:     # Specific context
    pass

@dataclass
class PizzaInventoryDto:    # Different view
    pass

πŸ’Ό Application Layer (application/)ΒΆ

The application layer orchestrates business operations through commands, queries, and handlers.

Commands (Write Operations):

# Pattern: {Verb}{Entity}Command (PascalCase)
@dataclass
class CreatePizzaCommand(Command[OperationResult[PizzaDto]]):
    pizza_name: str         # snake_case fields
    base_price: Decimal
    ingredient_ids: List[str]

@dataclass
class UpdatePizzaCommand(Command[OperationResult[PizzaDto]]):
    pizza_id: str
    pizza_name: Optional[str] = None
    base_price: Optional[Decimal] = None

@dataclass
class DeletePizzaCommand(Command[OperationResult]):
    pizza_id: str

# Complex business operations
@dataclass
class ProcessOrderPaymentCommand(Command[OperationResult[PaymentDto]]):
    order_id: str
    payment_method: PaymentMethod
    amount: Decimal

Queries (Read Operations):

# Pattern: {Action}{Entity}Query (PascalCase)
@dataclass
class GetPizzaByIdQuery(Query[PizzaDto]):
    pizza_id: str

@dataclass
class GetPizzasByTypeQuery(Query[List[PizzaDto]]):
    pizza_type: PizzaType
    include_unavailable: bool = False

@dataclass
class SearchPizzasQuery(Query[List[PizzaDto]]):
    search_term: str
    max_results: int = 50

# Complex queries with business logic
@dataclass
class GetPopularPizzasForRegionQuery(Query[List[PizzaDto]]):
    region_code: str
    date_range: DateRange
    min_order_count: int = 10

Handlers:

# Pattern: {Command/Query}Handler (PascalCase)
class CreatePizzaCommandHandler(CommandHandler[CreatePizzaCommand, OperationResult[PizzaDto]]):
    def __init__(self,
                 pizza_repository: PizzaRepository,
                 mapper: Mapper,
                 event_bus: EventBus):
        self._pizza_repository = pizza_repository    # snake_case fields
        self._mapper = mapper
        self._event_bus = event_bus

    async def handle_async(self, command: CreatePizzaCommand) -> OperationResult[PizzaDto]:
        # snake_case method names and variables
        validation_result = await self._validate_command(command)
        if not validation_result.is_success:
            return validation_result

        pizza = Pizza(
            name=command.pizza_name,
            base_price=command.base_price
        )

        await self._pizza_repository.save_async(pizza)

        # Raise domain event
        pizza_created_event = PizzaCreatedEvent(
            pizza_id=pizza.id,
            pizza_name=pizza.name
        )
        await self._event_bus.publish_async(pizza_created_event)

        return self.created(self._mapper.map(pizza, PizzaDto))

Services:

# Pattern: {Entity}Service or {Purpose}Service (PascalCase)
class PizzaService:
    async def calculate_cooking_time_async(self, pizza: Pizza) -> int:
        pass

    async def check_ingredient_availability_async(self, ingredients: List[str]) -> bool:
        pass

class OrderService:
    async def process_order_async(self, order: Order) -> OperationResult[Order]:
        pass

class PaymentService:
    async def process_payment_async(self, payment_info: PaymentInfo) -> PaymentResult:
        pass

# Specialized services
class PizzaRecommendationService:
    pass

class OrderNotificationService:
    pass

πŸ›οΈ Domain Layer (domain/)ΒΆ

The domain layer contains core business logic and entities, following domain-driven design principles.

Entities:

# Pattern: {BusinessConcept} (PascalCase, singular)
class Pizza(Entity[str]):
    def __init__(self, name: str, base_price: Decimal):
        super().__init__()
        self.name = name                    # snake_case attributes
        self.base_price = base_price
        self.available_sizes = []
        self.created_at = datetime.now()

    # Business method names in snake_case
    def add_ingredient(self, ingredient: Ingredient) -> None:
        if not self.can_add_ingredient(ingredient):
            raise BusinessRuleViolation("Cannot add ingredient to pizza")

        self.ingredients.append(ingredient)
        self.raise_event(IngredientAddedEvent(self.id, ingredient.id))

    def calculate_total_price(self, size: PizzaSize) -> Decimal:
        base_cost = self.base_price * size.price_multiplier
        ingredient_cost = sum(i.price for i in self.ingredients)
        return base_cost + ingredient_cost

    def can_add_ingredient(self, ingredient: Ingredient) -> bool:
        # Business rules
        return len(self.ingredients) < self.MAX_INGREDIENTS

class Order(Entity[str]):
    def __init__(self, customer_id: str):
        super().__init__()
        self.customer_id = customer_id
        self.order_items = []
        self.status = OrderStatus.PENDING
        self.total_amount = Decimal("0")

    def add_pizza(self, pizza: Pizza, quantity: int) -> None:
        if self.status != OrderStatus.PENDING:
            raise BusinessRuleViolation("Cannot modify confirmed order")

        order_item = OrderItem(pizza, quantity)
        self.order_items.append(order_item)
        self._recalculate_total()

        self.raise_event(PizzaAddedToOrderEvent(self.id, pizza.id, quantity))

Value Objects:

# Pattern: {Concept} (PascalCase, represents a value)
@dataclass(frozen=True)
class Money:
    amount: Decimal
    currency: str = "USD"

    def add(self, other: 'Money') -> 'Money':
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)

@dataclass(frozen=True)
class Address:
    street: str
    city: str
    state: str
    zip_code: str
    country: str = "USA"

    def to_display_string(self) -> str:
        return f"{self.street}, {self.city}, {self.state} {self.zip_code}"

@dataclass(frozen=True)
class EmailAddress:
    value: str

    def __post_init__(self):
        if "@" not in self.value:
            raise ValueError("Invalid email address")

Domain Events:

# Pattern: {Entity}{Action}Event (PascalCase)
@dataclass
class PizzaCreatedEvent(DomainEvent[str]):
    pizza_id: str
    pizza_name: str
    created_by: str
    created_at: datetime = field(default_factory=datetime.now)

@dataclass
class OrderConfirmedEvent(DomainEvent[str]):
    order_id: str
    customer_id: str
    total_amount: Decimal
    estimated_delivery: datetime

@dataclass
class PaymentProcessedEvent(DomainEvent[str]):
    payment_id: str
    order_id: str
    amount: Decimal
    payment_method: str

# Complex business events
@dataclass
class PizzaCustomizationCompletedEvent(DomainEvent[str]):
    pizza_id: str
    customization_options: Dict[str, Any]
    final_price: Decimal

Repository Interfaces:

# Pattern: {Entity}Repository (PascalCase)
class PizzaRepository(Repository[Pizza, str]):
    @abstractmethod
    async def get_by_name_async(self, name: str) -> Optional[Pizza]:
        pass

    @abstractmethod
    async def get_popular_pizzas_async(self, limit: int = 10) -> List[Pizza]:
        pass

    @abstractmethod
    async def search_by_ingredients_async(self, ingredients: List[str]) -> List[Pizza]:
        pass

class OrderRepository(Repository[Order, str]):
    @abstractmethod
    async def get_by_customer_id_async(self, customer_id: str) -> List[Order]:
        pass

    @abstractmethod
    async def get_pending_orders_async(self) -> List[Order]:
        pass

Business Exceptions:

# Pattern: {Reason}Exception (PascalCase)
class BusinessRuleViolation(Exception):
    def __init__(self, message: str, rule_name: str = None):
        super().__init__(message)
        self.rule_name = rule_name

class PizzaNotAvailableException(BusinessRuleViolation):
    def __init__(self, pizza_name: str):
        super().__init__(f"Pizza '{pizza_name}' is not available")
        self.pizza_name = pizza_name

class InsufficientInventoryException(BusinessRuleViolation):
    def __init__(self, ingredient_name: str, requested: int, available: int):
        super().__init__(f"Insufficient {ingredient_name}: requested {requested}, available {available}")
        self.ingredient_name = ingredient_name

πŸ”Œ Integration Layer (integration/)ΒΆ

The integration layer handles external systems, databases, and infrastructure concerns.

Repository Implementations:

# Pattern: {Technology}{Entity}Repository (PascalCase)
class MongoDbPizzaRepository(PizzaRepository):
    def __init__(self, database: Database):
        self._collection = database.pizzas    # snake_case field

    async def save_async(self, pizza: Pizza) -> None:
        pizza_doc = {
            "_id": pizza.id,
            "name": pizza.name,
            "base_price": float(pizza.base_price),
            "ingredients": [i.to_dict() for i in pizza.ingredients],
            "created_at": pizza.created_at.isoformat()
        }
        await self._collection.insert_one(pizza_doc)

    async def get_by_id_async(self, pizza_id: str) -> Optional[Pizza]:
        doc = await self._collection.find_one({"_id": pizza_id})
        return self._map_to_pizza(doc) if doc else None

class InMemoryPizzaRepository(PizzaRepository):
    def __init__(self):
        self._store: Dict[str, Pizza] = {}    # snake_case field

    async def save_async(self, pizza: Pizza) -> None:
        self._store[pizza.id] = pizza

External Service Clients:

# Pattern: {Service}Client or {System}Service (PascalCase)
class PaymentGatewayClient:
    def __init__(self, api_key: str, base_url: str):
        self._api_key = api_key
        self._base_url = base_url
        self._http_client = httpx.AsyncClient()

    async def process_payment_async(self, payment_request: PaymentRequest) -> PaymentResponse:
        pass

    async def refund_payment_async(self, transaction_id: str) -> RefundResponse:
        pass

class EmailNotificationService:
    async def send_order_confirmation_async(self, order: Order, customer_email: str) -> None:
        pass

    async def send_delivery_notification_async(self, order: Order) -> None:
        pass

class InventoryManagementService:
    async def check_ingredient_availability_async(self, ingredient_id: str) -> int:
        pass

    async def reserve_ingredients_async(self, reservations: List[IngredientReservation]) -> bool:
        pass

Configuration Models:

# Pattern: {Purpose}Settings or {System}Config (PascalCase)
class DatabaseSettings(BaseSettings):
    connection_string: str           # snake_case fields
    database_name: str
    connection_timeout: int = 30

    class Config:
        env_prefix = "DATABASE_"

class ApiSettings(BaseSettings):
    host: str = "0.0.0.0"
    port: int = 8000
    debug: bool = False
    cors_origins: List[str] = ["*"]

class PaymentGatewaySettings(BaseSettings):
    api_key: str
    webhook_secret: str
    sandbox_mode: bool = True
    timeout_seconds: int = 30

πŸ§ͺ Testing Naming ConventionsΒΆ

Consistent test naming makes it easy to understand what's being tested and why tests might be failing.

Test FilesΒΆ

# Pattern: test_{module_under_test}.py
test_pizza_service.py
test_order_controller.py
test_create_pizza_command_handler.py
test_mongo_pizza_repository.py

Test ClassesΒΆ

# Pattern: Test{ClassUnderTest}
class TestPizzaService:
    pass

class TestCreatePizzaCommandHandler:
    pass

class TestOrderController:
    pass

Test MethodsΒΆ

class TestPizzaService:
    # Pattern: test_{method}_{scenario}_{expected_result}
    def test_calculate_total_price_with_large_pizza_returns_correct_amount(self):
        pass

    def test_add_ingredient_with_max_ingredients_raises_exception(self):
        pass

    def test_create_pizza_with_valid_data_returns_success(self):
        pass

    # Async tests
    @pytest.mark.asyncio
    async def test_save_pizza_async_with_valid_data_saves_successfully(self):
        pass

    @pytest.mark.asyncio
    async def test_get_pizza_by_id_async_with_nonexistent_id_returns_none(self):
        pass

Test Fixtures and UtilitiesΒΆ

# Pattern: create_{entity} or {entity}_fixture
@pytest.fixture
def pizza_fixture():
    return Pizza(name="Margherita", base_price=Decimal("12.99"))

@pytest.fixture
def customer_fixture():
    return Customer(name="Mario", email="mario@test.com")

def create_test_pizza(name: str = "Test Pizza") -> Pizza:
    return Pizza(name=name, base_price=Decimal("10.00"))

def create_mock_repository() -> Mock:
    repository = Mock(spec=PizzaRepository)
    repository.save_async.return_value = None
    return repository

πŸ”„ Case Conversion PatternsΒΆ

The framework provides utilities to handle different naming conventions across system boundaries.

API Boundary ConversionΒΆ

# Internal Python code uses snake_case
class PizzaService:
    def __init__(self):
        self.total_price = Decimal("0")      # snake_case
        self.cooking_time = 15
        self.ingredient_list = []

# API DTOs can use camelCase for frontend compatibility
class PizzaDto(CamelModel):
    pizza_name: str          # Becomes "pizzaName" in JSON
    base_price: Decimal      # Becomes "basePrice" in JSON
    cooking_time: int        # Becomes "cookingTime" in JSON
    ingredient_list: List[str]  # Becomes "ingredientList" in JSON

# Frontend receives camelCase JSON
{
    "pizzaName": "Margherita",
    "basePrice": 12.99,
    "cookingTime": 15,
    "ingredientList": ["tomato", "mozzarella", "basil"]
}

Database Field MappingΒΆ

# Python entity with snake_case
class Pizza(Entity):
    def __init__(self):
        self.pizza_name = ""         # snake_case in Python
        self.base_price = Decimal("0")
        self.created_at = datetime.now()

# MongoDB document mapping
pizza_document = {
    "pizza_name": pizza.pizza_name,      # snake_case in database
    "base_price": float(pizza.base_price),
    "created_at": pizza.created_at.isoformat()
}

# SQL table with snake_case columns
CREATE TABLE pizzas (
    pizza_id UUID PRIMARY KEY,
    pizza_name VARCHAR(100) NOT NULL,    -- snake_case columns
    base_price DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

πŸ“ File and Directory NamingΒΆ

Consistent file organization makes codebases navigable and maintainable.

File Naming PatternsΒΆ

# βœ… Correct - snake_case for all files
pizza_service.py
create_order_command.py
mongo_pizza_repository.py
order_controller.py
pizza_created_event.py

# ❌ Incorrect
PizzaService.py
CreateOrderCommand.py
MongoPizzaRepository.py
OrderController.py

Directory StructureΒΆ

src/marios_pizzeria/
β”œβ”€β”€ api/                          # Layer directories
β”‚   β”œβ”€β”€ controllers/              # Component type directories
β”‚   β”‚   β”œβ”€β”€ pizzas_controller.py  # Plural for REST controllers
β”‚   β”‚   β”œβ”€β”€ orders_controller.py
β”‚   β”‚   └── customers_controller.py
β”‚   └── dtos/
β”‚       β”œβ”€β”€ pizza_dto.py          # Singular entity + dto
β”‚       β”œβ”€β”€ create_pizza_dto.py   # Action + entity + dto
β”‚       └── order_dto.py
β”œβ”€β”€ application/
β”‚   β”œβ”€β”€ commands/
β”‚   β”‚   β”œβ”€β”€ pizzas/               # Group by entity
β”‚   β”‚   β”‚   β”œβ”€β”€ create_pizza_command.py
β”‚   β”‚   β”‚   β”œβ”€β”€ update_pizza_command.py
β”‚   β”‚   β”‚   └── delete_pizza_command.py
β”‚   β”‚   └── orders/
β”‚   β”‚       β”œβ”€β”€ create_order_command.py
β”‚   β”‚       └── confirm_order_command.py
β”‚   β”œβ”€β”€ queries/
β”‚   β”‚   β”œβ”€β”€ get_pizza_by_id_query.py
β”‚   β”‚   └── search_pizzas_query.py
β”‚   └── handlers/
β”‚       β”œβ”€β”€ create_pizza_handler.py
β”‚       └── process_order_handler.py
β”œβ”€β”€ domain/
β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”œβ”€β”€ pizza.py              # Singular entity names
β”‚   β”‚   β”œβ”€β”€ order.py
β”‚   β”‚   └── customer.py
β”‚   β”œβ”€β”€ events/
β”‚   β”‚   β”œβ”€β”€ pizza_events.py       # Group related events
β”‚   β”‚   └── order_events.py
β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   β”œβ”€β”€ pizza_repository.py   # Abstract interfaces
β”‚   β”‚   └── order_repository.py
β”‚   └── exceptions/
β”‚       β”œβ”€β”€ business_exceptions.py
β”‚       └── validation_exceptions.py
└── integration/
    β”œβ”€β”€ repositories/
    β”‚   β”œβ”€β”€ mongodb_pizza_repository.py  # Technology + entity + repository
    β”‚   └── postgres_order_repository.py
    β”œβ”€β”€ services/
    β”‚   β”œβ”€β”€ payment_gateway_client.py
    β”‚   └── email_notification_service.py
    └── configuration/
        β”œβ”€β”€ database_settings.py
        └── api_settings.py

⚑ Common Anti-Patterns to Avoid¢

❌ Inconsistent Casing¢

# ❌ Mixed conventions in same context
class PizzaService:
    def __init__(self):
        self.pizzaName = ""          # camelCase in Python
        self.base_price = Decimal("0")  # snake_case
        self.CookingTime = 15        # PascalCase

# βœ… Consistent snake_case
class PizzaService:
    def __init__(self):
        self.pizza_name = ""
        self.base_price = Decimal("0")
        self.cooking_time = 15

❌ Unclear Abbreviations¢

# ❌ Cryptic abbreviations
class PzSvc:           # Pizza Service?
    def calc_ttl_prc(self):  # Calculate total price?
        pass

def proc_ord(ord_id):  # Process order?
    pass

# βœ… Clear, descriptive names
class PizzaService:
    def calculate_total_price(self):
        pass

def process_order(order_id: str):
    pass

❌ Misleading Names¢

# ❌ Name doesn't match behavior
class PizzaService:
    def get_pizza(self, pizza_id: str):
        # Actually creates and saves a new pizza!
        pizza = Pizza("New Pizza", Decimal("10.00"))
        self.repository.save(pizza)
        return pizza

# βœ… Name matches behavior
class PizzaService:
    def create_pizza(self, name: str, price: Decimal) -> Pizza:
        pizza = Pizza(name, price)
        self.repository.save(pizza)
        return pizza

    def get_pizza(self, pizza_id: str) -> Optional[Pizza]:
        return self.repository.get_by_id(pizza_id)

❌ Generic Names¢

# ❌ Too generic
class Manager:
    pass

class Helper:
    pass

def process(data):
    pass

# βœ… Specific and descriptive
class OrderManager:
    pass

class PizzaValidationHelper:
    pass

def process_payment_transaction(payment_info: PaymentInfo):
    pass

🎯 Framework-Specific Patterns¢

Command and Query NamingΒΆ

# Commands (imperative, action-oriented)
class CreatePizzaCommand:         # Create + Entity + Command
class UpdateOrderStatusCommand:   # Update + Entity + Attribute + Command
class ProcessPaymentCommand:      # Process + Concept + Command
class CancelOrderCommand:         # Cancel + Entity + Command

# Queries (descriptive, question-oriented)
class GetPizzaByIdQuery:          # Get + Entity + Criteria + Query
class FindOrdersByCustomerQuery:  # Find + Entity + Criteria + Query
class SearchPizzasQuery:          # Search + Entity + Query
class CountActiveOrdersQuery:     # Count + Description + Query

Event NamingΒΆ

# Domain Events (past tense, what happened)
class PizzaCreatedEvent:          # Entity + Action + Event
class OrderConfirmedEvent:        # Entity + Action + Event
class PaymentProcessedEvent:      # Concept + Action + Event
class InventoryUpdatedEvent:      # System + Action + Event

# Integration Events (external system communication)
class CustomerRegisteredIntegrationEvent:  # Action + Integration + Event
class OrderShippedIntegrationEvent:        # Action + Integration + Event

Repository NamingΒΆ

# Abstract repositories (domain layer)
class PizzaRepository(Repository[Pizza, str]):
    pass

# Concrete implementations (integration layer)
class MongoDbPizzaRepository(PizzaRepository):
    pass

class PostgreSqlOrderRepository(OrderRepository):
    pass

class InMemoryCustomerRepository(CustomerRepository):  # For testing
    pass

πŸš€ Best Practices SummaryΒΆ

βœ… Do'sΒΆ

  1. Be Consistent: Use the same patterns throughout your codebase
  2. Be Descriptive: Names should clearly indicate purpose and behavior
  3. Follow Layer Conventions: Different layers have different naming patterns
  4. Use Standard Suffixes: Command, Query, Handler, Repository, Service, etc.
  5. Group Related Items: Use directories and modules to organize related code
  6. Consider Context: API DTOs might need different casing than internal models
  7. Test Names Should Tell Stories: Long, descriptive test method names are good

❌ Don'ts¢

  1. Don't Mix Conventions: Pick snake_case or camelCase and stick to it within context
  2. Don't Use Abbreviations: Prefer customer_service over cust_svc
  3. Don't Use Generic Names: Avoid Manager, Helper, Utility without context
  4. Don't Ignore Framework Patterns: Follow established Command/Query/Handler patterns
  5. Don't Violate Layer Naming: Controllers in API layer, Handlers in Application layer
  6. Don't Use Misleading Names: Names should match actual behavior
  7. Don't Skip Namespace Prefixes: Use clear module organization

πŸ• Mario's Pizzeria ExampleΒΆ

Here's how all these conventions work together in a complete feature:

# Domain Layer - domain/entities/pizza.py
class Pizza(Entity[str]):
    def __init__(self, name: str, base_price: Decimal):
        super().__init__()
        self.name = name
        self.base_price = base_price
        self.toppings = []

    def add_topping(self, topping: Topping) -> None:
        if len(self.toppings) >= self.MAX_TOPPINGS:
            raise TooManyToppingsException(self.name)

        self.toppings.append(topping)
        self.raise_event(ToppingAddedEvent(self.id, topping.id))

# Domain Layer - domain/events/pizza_events.py
@dataclass
class PizzaCreatedEvent(DomainEvent[str]):
    pizza_id: str
    pizza_name: str
    base_price: Decimal

# Application Layer - application/commands/create_pizza_command.py
@dataclass
class CreatePizzaCommand(Command[OperationResult[PizzaDto]]):
    pizza_name: str
    base_price: Decimal
    topping_ids: List[str]

# Application Layer - application/handlers/create_pizza_handler.py
class CreatePizzaCommandHandler(CommandHandler[CreatePizzaCommand, OperationResult[PizzaDto]]):
    async def handle_async(self, command: CreatePizzaCommand) -> OperationResult[PizzaDto]:
        pizza = Pizza(command.pizza_name, command.base_price)
        await self._pizza_repository.save_async(pizza)
        return self.created(self._mapper.map(pizza, PizzaDto))

# API Layer - api/controllers/pizzas_controller.py
class PizzasController(ControllerBase):
    @post("/", response_model=PizzaDto, status_code=201)
    async def create_pizza(self, create_dto: CreatePizzaDto) -> PizzaDto:
        command = self.mapper.map(create_dto, CreatePizzaCommand)
        result = await self.mediator.execute_async(command)
        return self.process(result)

# Integration Layer - integration/repositories/mongodb_pizza_repository.py
class MongoDbPizzaRepository(PizzaRepository):
    async def save_async(self, pizza: Pizza) -> None:
        document = self._map_to_document(pizza)
        await self._collection.insert_one(document)

This example demonstrates how naming conventions create a clear, navigable codebase where each component's purpose and location are immediately obvious.