Tutorial Part 2: Services and Infrastructure¶
In this part of the tutorial, we'll implement the interfaces defined in the Domain Layer (01-domain.md
) and set up basic infrastructure components like logging and configuration placeholders. We'll focus on the Data Access Layer, Service Layer (Business Logic), and Infrastructure Layer.
We assume you have an instance of ServiceCollection
available, created via sc = create_service_collection()
from wd.di
.
Infrastructure Layer¶
Let's start with some basic infrastructure components. These often include logging, configuration models, or clients for external services.
File: infrastructure/logging_service.py
(Conceptual)
# For the tutorial, we'll use interfaces defined in 01-domain.md
# from domain.interfaces import ILogger
# Assume sc is an instance of ServiceCollection from wd.di
# from wd.di import create_service_collection
# sc = create_service_collection()
# @sc.singleton(ILogger) # Registering the concrete logger
class ConsoleLogger: # Implements ILogger structurally for simplicity here
def log_info(self, message: str):
print(f"[INFO] {message}")
def log_error(self, message: str, exception: Exception | None = None):
if exception:
print(f"[ERROR] {message} - Exception: {exception}")
else:
print(f"[ERROR] {message}")
File: infrastructure/config_models.py
(Conceptual)
from dataclasses import dataclass
@dataclass
class AppConfig:
"""Represents application-level configuration settings."""
service_name: str = "OrderProcessorTutorial"
default_user: str = "system"
debug_mode: bool = False
@dataclass
class EmailServiceOptions:
"""Configuration specific to an email service."""
smtp_host: str = "localhost"
smtp_port: int = 2525 # Default for MailHog/MailCatcher
sender_address: str = "noreply@example.com"
Data Access Layer (DAL)¶
This layer is responsible for data persistence. It implements the repository interfaces defined in the Domain Layer.
File: data/repository.py
(Conceptual)
# Using interfaces from domain.interfaces (see 01-domain.md)
# from domain.interfaces import IOrderRepository, ILogger, Order
# from infrastructure.logging_service import ConsoleLogger # Or inject ILogger
# Assume sc is an instance of ServiceCollection
# from wd.di import create_service_collection
# sc = create_service_collection()
# @sc.singleton(IOrderRepository)
class InMemoryOrderRepository: # Implements IOrderRepository structurally
def __init__(self, logger: /*ILogger from domain.interfaces*/ object):
self._logger = logger
self._orders_db = {}
self._logger.log_info("InMemoryOrderRepository initialized.")
def save_order(self, order: /*Order from domain.models*/ object):
self._logger.log_info(f"DAL: Saving order {order.order_id}")
self._orders_db[order.order_id] = order
self._logger.log_info(f"DAL: Order {order.order_id} saved.")
def get_order_by_id(self, order_id: str) -> /*Order | None from domain.models*/ object | None:
self._logger.log_info(f"DAL: Attempting to retrieve order {order_id}")
order = self._orders_db.get(order_id)
if order:
self._logger.log_info(f"DAL: Order {order_id} found.")
else:
self._logger.log_info(f"DAL: Order {order_id} not found.")
return order
InMemoryOrderRepository
uses a simple dictionary as an in-memory database. It depends on ILogger
(which ConsoleLogger
implements) for logging its actions.
Service Layer (Business Logic)¶
This layer contains the core business logic of the application. It orchestrates operations using services from the Data Access Layer and other business services.
File: services/order_processing_service.py
(Conceptual)
# Using interfaces and models from domain (see 01-domain.md)
# from domain.interfaces import IOrderService, IOrderRepository, ILogger, Order
# from infrastructure.logging_service import ConsoleLogger # Or inject ILogger
# Assume sc is an instance of ServiceCollection
# from wd.di import create_service_collection
# sc = create_service_collection()
# @sc.singleton(IOrderService)
class OrderProcessingService: # Implements IOrderService structurally
def __init__(self,
order_repository: /*IOrderRepository*/ object,
logger: /*ILogger*/ object):
self._order_repository = order_repository
self._logger = logger
self._logger.log_info("OrderProcessingService initialized.")
def process_new_order(self, order: /*Order from domain.models*/ object) -> bool:
self._logger.log_info(f"SERVICE: Processing new order {order.order_id} for item '{order.item}'")
# Basic validation (example)
if order.quantity <= 0 or order.price <= 0:
self._logger.log_error(f"SERVICE: Invalid order data for {order.order_id}. Quantity/Price must be positive.")
return False
# Simulate some processing, e.g., payment, inventory check (not shown)
self._logger.log_info(f"SERVICE: Order {order.order_id} passed validation and pre-processing.")
try:
self._order_repository.save_order(order)
self._logger.log_info(f"SERVICE: Order {order.order_id} successfully saved by repository.")
# Here you might trigger notifications, update other systems, etc.
return True
except Exception as e:
self._logger.log_error(f"SERVICE: Failed to save order {order.order_id}", exception=e)
return False
OrderProcessingService
implements IOrderService
. It depends on an IOrderRepository
to save order data and an ILogger
for logging. Its process_new_order
method contains the (simplified) business logic for handling a new order.
In the next part, we'll create the Presentation Layer and wire all these components together using WD-DI.
(Note: For actual type hints like /*ILogger from domain.interfaces*/ object
, you would use the real imported types. The comments are for illustrative cross-referencing to 01-domain.md
within this markdown context.)