Documentation Index
Fetch the complete documentation index at: https://docs.tasteful.heka.ai/llms.txt
Use this file to discover all available pages before exploring further.
Services
Services are the business logic layer of Tasteful flavors. They contain all the core business rules, orchestrate operations between different components, and provide a clean interface for controllers to interact with your application’s functionality. Services are where ALL business logic should live.
What is a Service?
A Service in Tasteful is a class that inherits from BaseService and encapsulates business logic. Services are responsible for:
- Business Logic Implementation: All core business rules and operations
- Data Orchestration: Coordinating operations between repositories and external systems
- Validation: Ensuring data integrity and business rule compliance
- Transaction Management: Handling complex operations that span multiple data sources
- Error Handling: Converting low-level errors into meaningful business exceptions
- Service Composition: Coordinating with other services for complex workflows
Services act as the single source of truth for business operations and should contain all logic that isn’t purely HTTP-related (controllers) or data-access related (repositories).
Core Concepts
BaseService Foundation
All services inherit from BaseService, which provides the foundation for dependency injection:
from tasteful.base_flavor import BaseService
class UserService(BaseService):
def __init__(self, user_repository: UserRepository):
super().__init__()
self.user_repository = user_repository
Key features of BaseService:
- Dependency Injection Support: Automatically receives dependencies through constructor injection
- Clean Architecture: Provides a clear separation between business logic and infrastructure
- Testability: Easy to mock and test in isolation
- Composition: Services can depend on other services and repositories
Service Dependencies
Services can depend on repositories, other services, and configuration objects:
class OrderService(BaseService):
def __init__(
self,
order_repository: OrderRepository,
user_service: UserService,
payment_service: PaymentService,
order_config: OrderConfig
):
super().__init__()
self.order_repository = order_repository
self.user_service = user_service
self.payment_service = payment_service
self.order_config = order_config
Implementation
Basic Service Implementation
Here’s a complete example of a service implementation:
from tasteful.base_flavor import BaseService
from typing import Dict, List, Optional
class ProductService(BaseService):
"""Service for product management business logic."""
def __init__(self, product_repository: ProductRepository):
super().__init__()
self.product_repository = product_repository
def list_products(self, skip: int = 0, limit: int = 100) -> List[Dict]:
"""List products with business logic applied."""
# Business logic: Apply default sorting and filtering
products = self.product_repository.list_products(
skip=skip,
limit=min(limit, 1000), # Business rule: max 1000 items
order_by="created_at",
active_only=True
)
# Business logic: Add computed fields
for product in products:
product["display_price"] = self._format_price(product["price"])
product["in_stock"] = product["quantity"] > 0
return products
def get_product(self, product_id: int) -> Optional[Dict]:
"""Get a product with business logic validation."""
if product_id <= 0:
raise ValueError("Product ID must be positive")
product = self.product_repository.get_by_id(product_id)
if not product:
raise ProductNotFoundError(f"Product {product_id} not found")
# Business logic: Add computed fields
product["display_price"] = self._format_price(product["price"])
product["availability_status"] = self._get_availability_status(product)
return product
def create_product(self, product_data: Dict) -> Dict:
"""Create a new product with validation."""
# Business validation
self._validate_product_data(product_data)
# Business logic: Set defaults
product_data["status"] = "active"
product_data["created_at"] = datetime.utcnow()
# Business logic: Generate SKU if not provided
if not product_data.get("sku"):
product_data["sku"] = self._generate_sku(product_data["name"])
return self.product_repository.create(product_data)
def update_product(self, product_id: int, product_data: Dict) -> Dict:
"""Update a product with business validation."""
# Check if product exists
existing_product = self.get_product(product_id)
# Business validation
self._validate_product_update(existing_product, product_data)
# Business logic: Update timestamp
product_data["updated_at"] = datetime.utcnow()
return self.product_repository.update(product_id, product_data)
def delete_product(self, product_id: int) -> Dict:
"""Soft delete a product with business rules."""
product = self.get_product(product_id)
# Business rule: Can't delete products with active orders
if self._has_active_orders(product_id):
raise BusinessRuleError("Cannot delete product with active orders")
# Business logic: Soft delete instead of hard delete
return self.product_repository.update(product_id, {
"status": "deleted",
"deleted_at": datetime.utcnow()
})
def _validate_product_data(self, product_data: Dict) -> None:
"""Private method for product validation."""
if not product_data.get("name"):
raise ValidationError("Product name is required")
if product_data.get("price", 0) < 0:
raise ValidationError("Product price cannot be negative")
def _format_price(self, price: float) -> str:
"""Format price for display."""
return f"${price:.2f}"
def _get_availability_status(self, product: Dict) -> str:
"""Determine product availability status."""
if product["quantity"] == 0:
return "out_of_stock"
elif product["quantity"] < 10:
return "low_stock"
else:
return "in_stock"
Advanced Patterns
Service Composition
Services can orchestrate complex operations by coordinating with multiple other services:
class OrderService(BaseService):
def __init__(
self,
order_repository: OrderRepository,
user_service: UserService,
product_service: ProductService,
payment_service: PaymentService,
inventory_service: InventoryService,
notification_service: NotificationService
):
super().__init__()
self.order_repository = order_repository
self.user_service = user_service
self.product_service = product_service
self.payment_service = payment_service
self.inventory_service = inventory_service
self.notification_service = notification_service
def create_order(self, order_data: Dict) -> Dict:
"""Create an order with complex business logic."""
# Step 1: Validate user
user = self.user_service.get_user(order_data["user_id"])
if not user["is_active"]:
raise BusinessRuleError("User account is not active")
# Step 2: Validate products and calculate totals
total_amount = 0
for item in order_data["items"]:
product = self.product_service.get_product(item["product_id"])
if not product:
raise ValidationError(f"Product {item['product_id']} not found")
# Business logic: Apply discounts, calculate totals
item_total = self._calculate_item_total(product, item["quantity"])
total_amount += item_total
# Step 3: Check inventory availability
inventory_check = self.inventory_service.check_availability(order_data["items"])
if not inventory_check["available"]:
raise BusinessRuleError("Some items are not available")
# Step 4: Create order record
order_data.update({
"total_amount": total_amount,
"status": "pending",
"created_at": datetime.utcnow()
})
order = self.order_repository.create(order_data)
# Step 5: Reserve inventory
self.inventory_service.reserve_items(order["id"], order_data["items"])
# Step 6: Send confirmation
self.notification_service.send_order_confirmation(user["email"], order)
return order
def process_payment(self, order_id: int, payment_data: Dict) -> Dict:
"""Process payment for an order."""
order = self.order_repository.get_by_id(order_id)
if not order:
raise OrderNotFoundError(f"Order {order_id} not found")
if order["status"] != "pending":
raise BusinessRuleError("Order is not in pending status")
try:
# Process payment through payment service
payment_result = self.payment_service.process_payment(
amount=order["total_amount"],
payment_data=payment_data
)
if payment_result["success"]:
# Update order status
self.order_repository.update(order_id, {
"status": "paid",
"payment_id": payment_result["payment_id"],
"paid_at": datetime.utcnow()
})
# Confirm inventory reservation
self.inventory_service.confirm_reservation(order_id)
# Send success notification
user = self.user_service.get_user(order["user_id"])
self.notification_service.send_payment_success(user["email"], order)
return {"success": True, "order": order}
else:
# Release inventory reservation
self.inventory_service.release_reservation(order_id)
return {"success": False, "error": payment_result["error"]}
except Exception as e:
# Release inventory on any error
self.inventory_service.release_reservation(order_id)
raise PaymentProcessingError(f"Payment processing failed: {str(e)}")
Service with Repository Integration
Services should always interact with data through repositories:
class UserService(BaseService):
def __init__(self, user_repository: UserRepository, auth_service: AuthService):
super().__init__()
self.user_repository = user_repository
self.auth_service = auth_service
def create_user(self, user_data: Dict) -> Dict:
"""Create a new user with business validation."""
# Business validation
self._validate_user_data(user_data)
# Business rule: Check if email already exists
if self.user_repository.email_exists(user_data["email"]):
raise BusinessRuleError("Email already exists")
# Business logic: Hash password
if "password" in user_data:
user_data["password_hash"] = self.auth_service.hash_password(user_data["password"])
del user_data["password"] # Remove plain password
# Business logic: Set defaults
user_data.update({
"is_active": True,
"created_at": datetime.utcnow(),
"email_verified": False
})
# Create user through repository
user = self.user_repository.create(user_data)
# Business logic: Send welcome email
self._send_welcome_email(user)
return user
def authenticate_user(self, email: str, password: str) -> Optional[Dict]:
"""Authenticate a user with business logic."""
user = self.user_repository.get_by_email(email)
if not user:
return None
# Business rule: Check if account is active
if not user["is_active"]:
raise BusinessRuleError("Account is deactivated")
# Verify password
if not self.auth_service.verify_password(password, user["password_hash"]):
# Business logic: Track failed login attempts
self._track_failed_login(user["id"])
return None
# Business logic: Update last login
self.user_repository.update(user["id"], {
"last_login": datetime.utcnow(),
"failed_login_attempts": 0
})
return user
Integration with Other Components
Service-Controller Relationship
Controllers should always delegate business logic to services:
# In Controller
class UserController(BaseController):
def __init__(self, user_service: UserService):
super().__init__(prefix="/users", tags=["users"])
self.user_service = user_service
@Post("/")
async def create_user(self, user_data: Dict) -> Dict:
# Controller delegates to service
return self.user_service.create_user(user_data)
@Post("/login")
async def login(self, credentials: Dict) -> Dict:
# Controller handles HTTP, service handles business logic
user = self.user_service.authenticate_user(
credentials["email"],
credentials["password"]
)
if user:
return {"success": True, "user": user}
else:
raise HTTPException(status_code=401, detail="Invalid credentials")
Service-Repository Relationship
Services should interact with data only through repositories:
# ✅ Good: Service uses repository for data access
class ProductService(BaseService):
def __init__(self, product_repository: ProductRepository):
super().__init__()
self.product_repository = product_repository
def get_product(self, product_id: int) -> Dict:
# Service uses repository for data access
product = self.product_repository.get_by_id(product_id)
if not product:
raise ProductNotFoundError("Product not found")
# Service adds business logic
product["display_price"] = self._format_price(product["price"])
return product
# ❌ Bad: Service directly accessing database
class ProductService(BaseService):
def __init__(self, database_connection):
super().__init__()
self.db = database_connection
def get_product(self, product_id: int) -> Dict:
# Don't access database directly from service
result = self.db.execute("SELECT * FROM products WHERE id = ?", product_id)
return result.fetchone()
Dependency Injection
Services receive their dependencies through constructor injection:
class OrderService(BaseService):
def __init__(
self,
order_repository: OrderRepository, # ← Repository dependency
user_service: UserService, # ← Service dependency
payment_service: PaymentService, # ← Service dependency
order_config: OrderConfig # ← Configuration dependency
):
super().__init__()
self.order_repository = order_repository
self.user_service = user_service
self.payment_service = payment_service
self.order_config = order_config
# In your flavor definition
class OrderFlavor(BaseFlavor):
def __init__(self):
super().__init__(
controller=OrderController,
services=[
OrderService,
UserService,
PaymentService
], # ← Services registered for injection
repositories=[OrderRepository, UserRepository],
config=OrderConfig
)
Best Practices
Do’s
- Put ALL business logic in services - Never put business logic in controllers or repositories
- Use meaningful method names - Service methods should clearly describe what business operation they perform
- Validate input data - Services should validate all input according to business rules
- Handle errors appropriately - Convert low-level errors into meaningful business exceptions
- Keep services focused - Each service should have a single responsibility
- Use dependency injection - Let the framework inject dependencies rather than creating them manually
class UserService(BaseService):
def __init__(self, user_repository: UserRepository, email_service: EmailService):
super().__init__()
self.user_repository = user_repository
self.email_service = email_service
def create_user(self, user_data: Dict) -> Dict:
"""Create a new user with full business logic."""
# Validate business rules
self._validate_user_creation(user_data)
# Apply business logic
user_data["status"] = "active"
user_data["created_at"] = datetime.utcnow()
# Create through repository
user = self.user_repository.create(user_data)
# Handle side effects
self.email_service.send_welcome_email(user["email"])
return user
def _validate_user_creation(self, user_data: Dict) -> None:
"""Private validation method."""
if not user_data.get("email"):
raise ValidationError("Email is required")
if self.user_repository.email_exists(user_data["email"]):
raise BusinessRuleError("Email already exists")
Don’ts
- Don’t put HTTP logic in services - Services shouldn’t know about HTTP requests/responses
- Don’t access external APIs directly - Use dedicated service classes for external integrations
- Don’t handle database connections directly - Always use repositories for data access
- Don’t make services too large - Break down complex services into smaller, focused services
- Don’t ignore error handling - Always handle and convert exceptions appropriately
# ❌ Bad: HTTP logic in service
class UserService(BaseService):
def create_user(self, request: HTTPRequest) -> HTTPResponse:
# Don't handle HTTP in services
user_data = request.json()
user = self.user_repository.create(user_data)
return HTTPResponse(json=user, status=201)
# ❌ Bad: Direct database access
class UserService(BaseService):
def __init__(self, database_url: str):
self.db = create_engine(database_url)
def get_user(self, user_id: int):
# Don't access database directly
with self.db.connect() as conn:
result = conn.execute("SELECT * FROM users WHERE id = ?", user_id)
return result.fetchone()
# ✅ Good: Clean service with proper separation
class UserService(BaseService):
def __init__(self, user_repository: UserRepository):
super().__init__()
self.user_repository = user_repository
def create_user(self, user_data: Dict) -> Dict:
# Pure business logic
self._validate_user_data(user_data)
return self.user_repository.create(user_data)
Common Patterns
CRUD Service Pattern
Most services follow standard CRUD patterns with business logic:
class ResourceService(BaseService):
def __init__(self, resource_repository: ResourceRepository):
super().__init__()
self.resource_repository = resource_repository
def list_resources(self, filters: Dict = None, pagination: Dict = None) -> List[Dict]:
"""List resources with business filtering."""
# Apply business rules to filters
safe_filters = self._sanitize_filters(filters or {})
safe_pagination = self._validate_pagination(pagination or {})
return self.resource_repository.list(safe_filters, safe_pagination)
def get_resource(self, resource_id: int) -> Dict:
"""Get a resource with business validation."""
if resource_id <= 0:
raise ValidationError("Invalid resource ID")
resource = self.resource_repository.get_by_id(resource_id)
if not resource:
raise ResourceNotFoundError("Resource not found")
return resource
def create_resource(self, resource_data: Dict) -> Dict:
"""Create a resource with business validation."""
self._validate_resource_data(resource_data)
# Apply business defaults
resource_data["status"] = "active"
resource_data["created_at"] = datetime.utcnow()
return self.resource_repository.create(resource_data)
def update_resource(self, resource_id: int, resource_data: Dict) -> Dict:
"""Update a resource with business validation."""
existing_resource = self.get_resource(resource_id)
# Business validation for updates
self._validate_resource_update(existing_resource, resource_data)
resource_data["updated_at"] = datetime.utcnow()
return self.resource_repository.update(resource_id, resource_data)
def delete_resource(self, resource_id: int) -> bool:
"""Delete a resource with business rules."""
resource = self.get_resource(resource_id)
# Business rule: Check if resource can be deleted
if self._has_dependencies(resource_id):
raise BusinessRuleError("Cannot delete resource with dependencies")
return self.resource_repository.delete(resource_id)
Service Orchestration Pattern
Services can orchestrate complex workflows:
class CheckoutService(BaseService):
def __init__(
self,
cart_service: CartService,
inventory_service: InventoryService,
payment_service: PaymentService,
order_service: OrderService,
notification_service: NotificationService
):
super().__init__()
self.cart_service = cart_service
self.inventory_service = inventory_service
self.payment_service = payment_service
self.order_service = order_service
self.notification_service = notification_service
def process_checkout(self, user_id: int, payment_data: Dict) -> Dict:
"""Process a complete checkout workflow."""
try:
# Step 1: Get cart
cart = self.cart_service.get_user_cart(user_id)
if not cart["items"]:
raise BusinessRuleError("Cart is empty")
# Step 2: Validate inventory
inventory_check = self.inventory_service.check_availability(cart["items"])
if not inventory_check["available"]:
raise BusinessRuleError("Some items are no longer available")
# Step 3: Create order
order = self.order_service.create_order_from_cart(cart)
# Step 4: Process payment
payment_result = self.payment_service.process_payment(
order["total"], payment_data
)
if not payment_result["success"]:
# Rollback order
self.order_service.cancel_order(order["id"])
raise PaymentError("Payment failed")
# Step 5: Confirm order
self.order_service.confirm_order(order["id"], payment_result["payment_id"])
# Step 6: Update inventory
self.inventory_service.reserve_items(order["items"])
# Step 7: Clear cart
self.cart_service.clear_cart(user_id)
# Step 8: Send notifications
self.notification_service.send_order_confirmation(user_id, order)
return {
"success": True,
"order_id": order["id"],
"payment_id": payment_result["payment_id"]
}
except Exception as e:
# Handle any errors and rollback if necessary
self._handle_checkout_error(e, locals())
raise
Testing
Services are highly testable thanks to dependency injection:
import pytest
from unittest.mock import Mock, patch
from datetime import datetime
def test_user_service_create_user():
# Arrange
mock_user_repository = Mock(spec=UserRepository)
mock_user_repository.email_exists.return_value = False
mock_user_repository.create.return_value = {"id": 1, "email": "test@example.com"}
mock_email_service = Mock(spec=EmailService)
user_service = UserService(
user_repository=mock_user_repository,
email_service=mock_email_service
)
user_data = {"email": "test@example.com", "name": "Test User"}
# Act
result = user_service.create_user(user_data)
# Assert
assert result["id"] == 1
assert result["email"] == "test@example.com"
mock_user_repository.email_exists.assert_called_once_with("test@example.com")
mock_user_repository.create.assert_called_once()
mock_email_service.send_welcome_email.assert_called_once_with("test@example.com")
def test_user_service_create_user_duplicate_email():
# Arrange
mock_user_repository = Mock(spec=UserRepository)
mock_user_repository.email_exists.return_value = True
user_service = UserService(user_repository=mock_user_repository)
user_data = {"email": "test@example.com", "name": "Test User"}
# Act & Assert
with pytest.raises(BusinessRuleError, match="Email already exists"):
user_service.create_user(user_data)
mock_user_repository.create.assert_not_called()
def test_order_service_complex_workflow():
# Arrange
mock_order_repository = Mock(spec=OrderRepository)
mock_user_service = Mock(spec=UserService)
mock_payment_service = Mock(spec=PaymentService)
mock_user_service.get_user.return_value = {"id": 1, "is_active": True}
mock_payment_service.process_payment.return_value = {"success": True, "payment_id": "pay_123"}
mock_order_repository.create.return_value = {"id": 1, "total_amount": 100.0}
order_service = OrderService(
order_repository=mock_order_repository,
user_service=mock_user_service,
payment_service=mock_payment_service
)
order_data = {"user_id": 1, "items": [{"product_id": 1, "quantity": 2}]}
# Act
result = order_service.create_order(order_data)
# Assert
assert result["id"] == 1
mock_user_service.get_user.assert_called_once_with(1)
mock_order_repository.create.assert_called_once()
Services are the heart of your Tasteful application’s business logic. They provide a clean, testable, and maintainable way to implement complex business operations while maintaining proper separation of concerns with controllers and repositories.