[SKILL_NAME] - Backend Logic Implementation
Role: Backend Logic Implementer Domain: Business Logic & Domain Models Created: [CURRENT_DATE]
Quick Reference
<!-- This section is extracted by Orchestrator for task prompts (~50-100 lines) -->When to use: Service Objects, Business Logic, Domain Models, Use Cases, Validation
Key Patterns:
- •
Service Object Pattern
- •One class per use case
- •
callmethod as entry point - •Dependency injection for repositories
- •Return Result/Either object (Success/Failure)
- •
Validation Pattern
- •Validate at start of
callmethod - •Fail fast with clear error messages
- •Use custom ValidationError exceptions
- •Validate at start of
- •
Error Handling
- •Custom exceptions for business errors
- •Wrap in transaction for rollback
- •Never expose internal errors to caller
- •
Domain Model Rules
- •Keep business logic in models/services, not controllers
- •Use Value Objects for concepts (Money, Email, etc.)
- •Publish Domain Events for side effects
Quick Example (Rails):
class Users::Register
def initialize(user_repo: UserRepository.new)
@user_repo = user_repo
end
def call(params)
validate!(params)
user = @user_repo.create(params)
Result.success(user: user)
rescue ValidationError => e
Result.failure(errors: e.messages)
end
private
def validate!(params)
raise ValidationError, 'Email required' if params[:email].blank?
raise ValidationError, 'Email taken' if @user_repo.exists?(email: params[:email])
end
end
Anti-Patterns to Avoid:
- •Fat controllers with business logic
- •Direct DB queries in service objects (use repositories)
- •God services doing too many things
- •Anemic models (data without behavior)
Purpose
Implement core business logic, domain models, and service orchestration for backend systems. Focus on clean architecture, SOLID principles, and maintainable business rules.
When to Activate
Use this skill for:
- •Implementing business logic and domain rules
- •Creating service objects and use cases
- •Building domain models and value objects
- •Orchestrating complex workflows
- •Implementing validation and business constraints
- •State management and business entities
Do NOT use for:
- •Database queries (use persistence-adapter)
- •External API calls (use integration-adapter)
- •Testing (use test-engineering)
Core Capabilities
1. Domain Model Design
- •Entity modeling with clear boundaries
- •Value objects for business concepts
- •Aggregate roots for consistency
- •Domain events for decoupling
2. Business Logic Implementation
- •Service objects for complex operations
- •Use cases for feature workflows
- •Policy objects for business rules
- •Calculator objects for computations
3. Validation & Constraints
- •Input validation
- •Business rule enforcement
- •State transition validation
- •Cross-entity constraints
4. Error Handling
- •Domain-specific exceptions
- •Error context and messaging
- •Graceful degradation
- •Recovery strategies
[TECH_STACK_SPECIFIC] Patterns
Ruby on Rails
# Service Object Pattern
class Users::CreateAccount
include ActiveModel::Validations
attr_reader :user, :errors
def initialize(params)
@params = params
@errors = ActiveModel::Errors.new(self)
end
def call
validate_params!
ActiveRecord::Base.transaction do
create_user
setup_profile
send_welcome_email
end
Success.new(user: @user)
rescue ValidationError => e
Failure.new(errors: e.errors)
end
private
def validate_params!
raise ValidationError, 'Email required' unless @params[:email].present?
raise ValidationError, 'Email taken' if User.exists?(email: @params[:email])
end
def create_user
@user = User.create!(
email: @params[:email],
password: @params[:password]
)
end
def setup_profile
@user.create_profile!(
name: @params[:name],
bio: @params[:bio]
)
end
def send_welcome_email
UserMailer.welcome(@user).deliver_later
end
end
# Value Object Pattern
class Money
include Comparable
attr_reader :amount, :currency
def initialize(amount, currency = 'USD')
@amount = BigDecimal(amount.to_s)
@currency = currency
end
def +(other)
raise CurrencyMismatch unless currency == other.currency
Money.new(amount + other.amount, currency)
end
def *(multiplier)
Money.new(amount * multiplier, currency)
end
def to_s
"#{currency} #{amount.round(2)}"
end
def <=>(other)
raise CurrencyMismatch unless currency == other.currency
amount <=> other.amount
end
end
# Policy Object Pattern
class OrderRefundPolicy
def initialize(order)
@order = order
end
def refundable?
return false if @order.refunded?
return false if days_since_purchase > 30
return false if @order.digital_goods? && @order.accessed?
true
end
def refund_amount
return Money.new(0) unless refundable?
if days_since_purchase <= 7
@order.total
elsif days_since_purchase <= 14
@order.total * 0.75
else
@order.total * 0.50
end
end
private
def days_since_purchase
(Time.current - @order.purchased_at) / 1.day
end
end
# Domain Event Pattern
class Order < ApplicationRecord
include ActiveModel::Dirty
after_commit :publish_status_change, if: :saved_change_to_status?
private
def publish_status_change
event = OrderStatusChanged.new(
order_id: id,
old_status: status_before_last_save,
new_status: status,
changed_at: updated_at
)
EventBus.publish(event)
end
end
Node.js / TypeScript
// Service Class Pattern
interface CreateAccountParams {
email: string;
password: string;
name: string;
bio?: string;
}
interface CreateAccountResult {
success: boolean;
user?: User;
errors?: ValidationError[];
}
class CreateAccountService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService
) {}
async execute(params: CreateAccountParams): Promise<CreateAccountResult> {
// Validate input
const validationErrors = this.validate(params);
if (validationErrors.length > 0) {
return { success: false, errors: validationErrors };
}
// Check uniqueness
const existingUser = await this.userRepository.findByEmail(params.email);
if (existingUser) {
return {
success: false,
errors: [{ field: 'email', message: 'Email already taken' }]
};
}
// Execute in transaction
const user = await this.userRepository.transaction(async (tx) => {
const user = await this.userRepository.create({
email: params.email,
password: await this.hashPassword(params.password)
}, tx);
await this.userRepository.createProfile({
userId: user.id,
name: params.name,
bio: params.bio
}, tx);
return user;
});
// Background job
await this.emailService.sendWelcome(user.email);
return { success: true, user };
}
private validate(params: CreateAccountParams): ValidationError[] {
const errors: ValidationError[] = [];
if (!params.email || !this.isValidEmail(params.email)) {
errors.push({ field: 'email', message: 'Invalid email format' });
}
if (!params.password || params.password.length < 8) {
errors.push({ field: 'password', message: 'Password must be at least 8 characters' });
}
return errors;
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
private async hashPassword(password: string): Promise<string> {
// Implementation
return 'hashed';
}
}
// Value Object Pattern
class Money {
private constructor(
public readonly amount: number,
public readonly currency: string = 'USD'
) {
if (amount < 0) {
throw new Error('Amount cannot be negative');
}
}
static create(amount: number, currency = 'USD'): Money {
return new Money(amount, currency);
}
add(other: Money): Money {
this.ensureSameCurrency(other);
return new Money(this.amount + other.amount, this.currency);
}
multiply(multiplier: number): Money {
return new Money(this.amount * multiplier, this.currency);
}
toString(): string {
return `${this.currency} ${this.amount.toFixed(2)}`;
}
private ensureSameCurrency(other: Money): void {
if (this.currency !== other.currency) {
throw new Error('Currency mismatch');
}
}
}
// Domain Event Pattern
interface DomainEvent {
occurredAt: Date;
eventType: string;
}
class OrderStatusChanged implements DomainEvent {
public readonly occurredAt = new Date();
public readonly eventType = 'OrderStatusChanged';
constructor(
public readonly orderId: string,
public readonly oldStatus: string,
public readonly newStatus: string
) {}
}
class Order {
private domainEvents: DomainEvent[] = [];
constructor(
public id: string,
public status: string
) {}
changeStatus(newStatus: string): void {
const oldStatus = this.status;
this.status = newStatus;
this.domainEvents.push(
new OrderStatusChanged(this.id, oldStatus, newStatus)
);
}
getDomainEvents(): DomainEvent[] {
return [...this.domainEvents];
}
clearDomainEvents(): void {
this.domainEvents = [];
}
}
Python / Django
# Service Layer Pattern
from dataclasses import dataclass
from typing import Optional
from django.db import transaction
@dataclass
class CreateAccountResult:
success: bool
user: Optional['User'] = None
errors: Optional[dict] = None
class CreateAccountService:
def __init__(self, user_repository, email_service):
self.user_repository = user_repository
self.email_service = email_service
def execute(self, email: str, password: str, name: str, bio: str = None) -> CreateAccountResult:
# Validate
errors = self._validate(email, password)
if errors:
return CreateAccountResult(success=False, errors=errors)
# Check uniqueness
if self.user_repository.exists_by_email(email):
return CreateAccountResult(
success=False,
errors={'email': 'Email already taken'}
)
# Execute in transaction
try:
with transaction.atomic():
user = self.user_repository.create(
email=email,
password=self._hash_password(password)
)
self.user_repository.create_profile(
user_id=user.id,
name=name,
bio=bio
)
# Background task
self.email_service.send_welcome.delay(user.email)
return CreateAccountResult(success=True, user=user)
except Exception as e:
return CreateAccountResult(
success=False,
errors={'general': str(e)}
)
def _validate(self, email: str, password: str) -> Optional[dict]:
errors = {}
if not email or '@' not in email:
errors['email'] = 'Invalid email format'
if not password or len(password) < 8:
errors['password'] = 'Password must be at least 8 characters'
return errors if errors else None
def _hash_password(self, password: str) -> str:
# Implementation
return 'hashed'
# Value Object Pattern
from decimal import Decimal
class Money:
def __init__(self, amount: Decimal, currency: str = 'USD'):
if amount < 0:
raise ValueError('Amount cannot be negative')
self.amount = amount
self.currency = currency
def __add__(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError('Currency mismatch')
return Money(self.amount + other.amount, self.currency)
def __mul__(self, multiplier: float) -> 'Money':
return Money(self.amount * Decimal(str(multiplier)), self.currency)
def __str__(self) -> str:
return f'{self.currency} {self.amount:.2f}'
def __eq__(self, other: 'Money') -> bool:
return self.amount == other.amount and self.currency == other.currency
Tools Required
MCP Servers
[MCP_TOOLS]
<!-- Populated during skill creation based on: 1. User's installed MCP servers 2. User's selection for this skill Recommended for this skill (examples): - None required for basic business logic implementation - Optional: Database inspection tools for debugging - Optional: Code analysis tools for complexity metrics Note: Skills work without MCP servers, but functionality may be limited -->Development Tools
- •Code editor with language support
- •Debugger for stepping through logic
- •REPL for testing logic fragments
Testing Tools
- •Unit testing framework
- •Test doubles (mocks, stubs)
- •Code coverage tools
Analysis Tools
- •Static analysis / linting
- •Complexity metrics
- •Code review tools
Quality Checklist
Before Implementation
- • Domain model clearly defined
- • Business rules documented
- • Edge cases identified
- • Validation requirements clear
During Implementation
- • Single Responsibility Principle followed
- • Business logic decoupled from framework
- • Clear method and variable names
- • Complex logic extracted to private methods
After Implementation
- • All business rules implemented
- • Edge cases handled
- • Error handling comprehensive
- • Unit tests cover all paths
- • Documentation updated
Code Review
- • Logic is testable without external dependencies
- • No database queries in business logic
- • No direct API calls in business logic
- • Proper separation of concerns
- • Domain events published appropriately
Testing Patterns
Unit Testing Business Logic
# RSpec example
RSpec.describe Users::CreateAccount do
describe '#call' do
let(:params) do
{
email: 'user@example.com',
password: 'password123',
name: 'John Doe'
}
end
it 'creates a new user' do
result = described_class.new(params).call
expect(result).to be_success
expect(result.user).to be_persisted
expect(result.user.email).to eq('user@example.com')
end
it 'creates a profile for the user' do
result = described_class.new(params).call
expect(result.user.profile).to be_present
expect(result.user.profile.name).to eq('John Doe')
end
it 'sends welcome email' do
expect {
described_class.new(params).call
}.to have_enqueued_job(UserMailerJob)
end
context 'when email is already taken' do
before { create(:user, email: 'user@example.com') }
it 'returns failure' do
result = described_class.new(params).call
expect(result).to be_failure
expect(result.errors).to include('Email taken')
end
end
end
end
// Jest example
describe('CreateAccountService', () => {
let service: CreateAccountService;
let userRepository: jest.Mocked<UserRepository>;
let emailService: jest.Mocked<EmailService>;
beforeEach(() => {
userRepository = {
findByEmail: jest.fn(),
create: jest.fn(),
createProfile: jest.fn(),
transaction: jest.fn((cb) => cb(null))
} as any;
emailService = {
sendWelcome: jest.fn()
} as any;
service = new CreateAccountService(userRepository, emailService);
});
describe('execute', () => {
const validParams = {
email: 'user@example.com',
password: 'password123',
name: 'John Doe'
};
it('creates a new user account', async () => {
userRepository.findByEmail.mockResolvedValue(null);
userRepository.create.mockResolvedValue({ id: '1', email: validParams.email });
const result = await service.execute(validParams);
expect(result.success).toBe(true);
expect(result.user).toBeDefined();
expect(userRepository.create).toHaveBeenCalled();
});
it('sends welcome email', async () => {
userRepository.findByEmail.mockResolvedValue(null);
userRepository.create.mockResolvedValue({ id: '1', email: validParams.email });
await service.execute(validParams);
expect(emailService.sendWelcome).toHaveBeenCalledWith(validParams.email);
});
it('returns error when email is taken', async () => {
userRepository.findByEmail.mockResolvedValue({ id: '2', email: validParams.email });
const result = await service.execute(validParams);
expect(result.success).toBe(false);
expect(result.errors).toContainEqual({
field: 'email',
message: 'Email already taken'
});
});
});
});
Common Patterns
Result Objects
Use Result/Either pattern for operation outcomes:
- •Success with data
- •Failure with errors
- •No exceptions for business rule violations
Command Pattern
Encapsulate operations as objects:
- •Clear intent
- •Easy to queue/schedule
- •Auditable actions
Strategy Pattern
Swap algorithms at runtime:
- •Payment processors
- •Pricing strategies
- •Notification channels
State Machine
Model complex state transitions:
- •Order fulfillment
- •Approval workflows
- •User onboarding
Anti-Patterns to Avoid
- •Fat Models: Don't put all logic in ActiveRecord/ORM models
- •God Services: Keep services focused on single use case
- •Anemic Domain Model: Models should have behavior, not just data
- •Transaction Script: Don't write procedural code in controllers
- •Hidden Dependencies: Inject dependencies explicitly
- •Primitive Obsession: Use value objects for domain concepts
Performance Considerations
- •Avoid N+1 queries (delegate to persistence layer)
- •Cache expensive calculations
- •Lazy load when appropriate
- •Consider async processing for slow operations
- •Profile before optimizing
Documentation Requirements
- •Document business rules and constraints
- •Explain non-obvious algorithms
- •Provide examples for complex workflows
- •Document state transitions
- •Keep ADRs for architectural decisions
Remember: Business logic should be framework-agnostic, testable in isolation, and clearly express domain concepts. Focus on correctness, clarity, and maintainability over premature optimization.