Service-Based Relationships Implementation
This page is current and ready for use.
Table of Contents
- Introduction
- Traditional vs Service-Based Relationships
- Implementation Guidelines
- Performance Optimization
- Code Examples
- Best Practices
Introduction
This guide outlines our approach to handling entity relationships using services instead of traditional Doctrine relationships. This implementation enhances performance, maintainability, and scalability in high-traffic applications.
Why Service-Based Relationships?
- Better control over data loading
- Improved performance for complex operations
- Cleaner entity classes
- Easier to maintain and scale
- Better suited for microservices architecture
Traditional vs Service-Based Relationships
Traditional Doctrine Approach
class User
{
#[ORM\OneToMany(targetEntity: UserEmail::class)]
private Collection $emails;
public function getEmails(): Collection
{
return $this->emails;
}
}
Service-Based Approach
// Clean Entity
class User
{
#[ORM\Column(length: 100)]
private string $name;
}
// Service Layer
class UserEmailService
{
public function getUserEmails(string $userId): array
{
return $this->repository->findByUserId($userId);
}
}
Implementation Guidelines
1. Entity Design
Entities should only contain essential fields:
#[ORM\Entity]
class UserEmail
{
#[ORM\Column(type: 'ulid')]
private string $userId; // Just the foreign key
#[ORM\Column]
private string $email;
}
2. Service Layer
Create dedicated services for relationship management:
class UserRelationshipService
{
public function __construct(
private UserEmailRepository $emailRepo,
private UserStatusRepository $statusRepo
) {}
public function getUserProfile(string $userId): array
{
return [
'emails' => $this->emailRepo->findByUserId($userId),
'status' => $this->statusRepo->findCurrentByUserId($userId)
];
}
}
3. Database Views
Create views for common relationship queries:
CREATE VIEW v_user_relationships AS
SELECT
u.id,
u.name,
e.email,
s.status
FROM users u
LEFT JOIN user_emails e ON u.id = e.user_id
LEFT JOIN user_statuses s ON u.id = s.user_id;
Performance Optimization
1. Indexing
-- Create efficient indexes for relationships
CREATE INDEX idx_user_emails_user_id ON user_emails(user_id);
CREATE INDEX idx_user_emails_primary ON user_emails(user_id, is_primary)
WHERE is_primary = true;
2. Optimized Queries
class UserEmailRepository
{
public function findPrimaryEmailsByUserIds(array $userIds): array
{
return $this->createQueryBuilder('ue')
->where('ue.userId IN (:userIds)')
->andWhere('ue.isPrimary = true')
->setParameter('userIds', $userIds)
->getQuery()
->getResult();
}
}
Code Examples
1. Basic Implementation
// Entity
class User
{
#[ORM\Column(length: 100)]
private string $name;
}
// Service
class UserService
{
public function getUserWithRelations(string $userId): array
{
$user = $this->repository->find($userId);
return [
'user' => $user,
'emails' => $this->emailService->getUserEmails($userId),
'status' => $this->statusService->getCurrentStatus($userId)
];
}
}
2. Batch Operations
class UserEmailService
{
public function addEmailsToUsers(array $userEmails): void
{
$this->entityManager->beginTransaction();
try {
foreach ($userEmails as $userId => $email) {
$this->addEmail($userId, $email);
}
$this->entityManager->commit();
} catch (\Exception $e) {
$this->entityManager->rollback();
throw $e;
}
}
}
Best Practices
1. Service Design
- One service per relationship type
- Clear method naming
- Proper error handling
- Transaction management
- Event dispatching for changes
2. Query Optimization
- Use database views for complex joins
- Implement batch operations
- Cache frequently accessed data
- Monitor query performance
3. Error Handling
class UserEmailService
{
public function addEmail(string $userId, string $email): void
{
if (!$this->userExists($userId)) {
throw new UserNotFoundException($userId);
}
if ($this->emailExists($email)) {
throw new DuplicateEmailException($email);
}
// Proceed with adding email
}
}
Migration Guide
When converting from traditional Doctrine relationships:
- Remove relationship annotations from entities
- Create service classes for each relationship type
- Update repository methods
- Create database views
- Add proper indexes
- Update application code to use services
- Test performance
- Monitor and optimize
Conclusion
Service-based relationships provide better control, performance, and maintainability compared to traditional Doctrine relationships. While they require more initial setup, the benefits in terms of scalability and maintainability make them ideal for large-scale applications.
For specific implementation questions or guidance, contact the development team lead.