Service Layer
Detailed documentation of oCore's service layer, responsibilities, dependencies, and patterns
The service layer is the core of oCore's backend. Services encapsulate all business logic, orchestrate database queries through Ent, interact with external systems (SSH, DNS, email, Git), and enqueue background jobs via River. Handlers never contain business logic -- they delegate to services.
Service Inventory
oCore's service layer consists of 50+ services organized by domain. The table below lists the major services and their responsibilities.
Authentication and Authorization
| Service | File | Responsibility |
|---|---|---|
AuthService | auth.go | Signup, login, email verification, password reset, OAuth |
TokenService | token.go | JWT access/refresh token generation and validation |
TOTPService | totp.go | TOTP two-factor authentication setup and verification |
WebAuthnService | webauthn.go | Passkey/WebAuthn registration and authentication |
RBACService | rbac.go | Role-based permission checking |
OAuthService | oauth.go | OAuth provider integration (GitHub, Google) |
PasswordResetService | passwordreset.go | Password reset token generation and redemption |
VerificationService | verification.go | Email verification token management |
Organization Management
| Service | File | Responsibility |
|---|---|---|
OrgService | org.go | Organization CRUD, slug generation, system role seeding |
OrgSettingsService | org_settings.go | Organization-level settings management |
InvitationService | invitation.go | Member invitations with email and token |
AgencyService | agency.go | Agency-client organization relationships |
APIKeyService | api_key.go | API key generation, hashing, and scope management |
Infrastructure Management
| Service | File | Responsibility |
|---|---|---|
ServerService | server.go | Server CRUD, SSH connectivity validation |
ProvisionerService | provisioner.go | Server provisioning automation |
InstanceService | instance.go | Odoo instance lifecycle management |
InstanceConfigService | instance_config.go | Instance configuration (odoo.conf) management |
InstanceComposeService | instance_compose.go | Docker Compose generation for instances |
PortAllocatorService | port_allocator.go | Port allocation (base 10000, gap of 10) |
CapacityService | capacity.go | Server capacity monitoring and planning |
Deployment Pipeline
| Service | File | Responsibility |
|---|---|---|
ProjectService | project.go | Git project management and repository linking |
EnvironmentService | environment.go | Environment lifecycle (staging, production) |
DeployService | deploy.go | Deployment orchestration and rollback |
BuildService | build.go | Instance build pipeline (Docker builds) |
CloneService | clone.go | Environment cloning with data |
PromotionService | promotion.go | Environment promotion (staging to production) |
TransferService | transfer.go | Cross-server transfer orchestration |
Database Management
| Service | File | Responsibility |
|---|---|---|
DatabaseService | database.go | PostgreSQL database lifecycle management |
DatabaseMaintenanceService | database_maintenance.go | VACUUM, ANALYZE, REINDEX operations |
DatabaseTuningService | database_tuning.go | Auto-tuning based on server resources |
DatabaseMetricsService | database_metrics.go | Query performance metrics collection |
DatabaseReplicasService | database_replicas.go | Read replica setup and management |
ImportExportService | db_import_export.go | Database import/export operations |
Backup and Recovery
| Service | File | Responsibility |
|---|---|---|
BackupService | backup.go | Backup creation, scheduling, and management |
BackupDestinationService | backup_destination.go | S3/local backup destination configuration |
BackupRestoreService | backup_restore.go | Backup restoration pipeline |
SnapshotService | snapshot.go | Filesystem snapshots for instant rollback |
DisasterRecoveryService | disaster_recovery.go | Disaster recovery planning and execution |
Monitoring and Observability
| Service | File | Responsibility |
|---|---|---|
MonitoringService | monitoring.go | Server and instance health monitoring |
MetricsService | metrics.go | Metrics aggregation and querying |
AlertService | alert.go | Alert rule evaluation and notification |
UptimeService | uptime.go | Uptime tracking and SLA reporting |
LogStreamService | log_stream.go | Real-time log streaming via SSE/WebSocket |
AuditService | audit.go | Audit trail recording |
Networking
| Service | File | Responsibility |
|---|---|---|
DomainService | domain.go | Custom domain configuration and SSL |
DNSConfigService | dns_config.go | DNS provider integration |
ProxyManagerService | proxy_manager.go | Reverse proxy management (Nginx, Traefik, NPM) |
HAProxyService | haproxy.go | HAProxy load balancer configuration |
IPAccessService | ip_access.go | IP whitelist/blacklist rules |
Service Dependencies
Services depend on each other and on shared infrastructure. Dependencies are injected through constructors.
Common Dependencies
Most services depend on:
*ent.Client-- Database access through the Ent ORM*river.Client[pgx.Tx]-- Job queue for async operations (services that trigger background work)- Other services when business logic spans multiple domains
Transaction Patterns
Single-Entity Transactions
Simple CRUD operations use Ent's built-in transactional methods:
func (s *OrgService) CreateOrganization(ctx context.Context, name, slug string, creatorID uuid.UUID) (*ent.Organization, error) {
// Ent handles the transaction internally
org, err := s.client.Organization.Create().
SetName(name).
SetSlug(slug).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("create organization: %w", err)
}
// Seed system roles within the same request context
if err := s.seedSystemRoles(ctx, org.ID); err != nil {
return nil, fmt.Errorf("seed roles: %w", err)
}
return org, nil
}Multi-Entity Transactions
When multiple entities need to be created atomically, services use Ent's explicit transaction API:
func (s *EnvironmentService) CreateEnvironment(ctx context.Context, req CreateEnvRequest) (*ent.Environment, error) {
tx, err := s.client.Tx(ctx)
if err != nil {
return nil, fmt.Errorf("start transaction: %w", err)
}
defer tx.Rollback() // No-op if committed
env, err := tx.Environment.Create().
SetName(req.Name).
SetProjectID(req.ProjectID).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("create environment: %w", err)
}
_, err = tx.DatabaseConfig.Create().
SetEnvironmentID(env.ID).
SetMaxConnections(100).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("create db config: %w", err)
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("commit: %w", err)
}
return env, nil
}Transactional Job Enqueue
Jobs can be enqueued within a database transaction, ensuring the job is only created if the transaction commits:
func (s *ServerService) DeleteServer(ctx context.Context, tx *ent.Tx, serverID uuid.UUID) error {
// Mark as removing within the transaction
_, err := tx.Server.UpdateOneID(serverID).
SetStatus("removing").
Save(ctx)
// Enqueue cleanup job -- only persisted if tx commits
_, err = s.riverClient.InsertTx(ctx, tx, jobargs.CleanupServerArgs{
ServerID: serverID,
}, nil)
return nil
}Error Handling Patterns
Services use wrapped errors with fmt.Errorf for context propagation:
// Always wrap errors with context
if err != nil {
return nil, fmt.Errorf("list servers for org %s: %w", orgID, err)
}Handlers check error types to determine the appropriate HTTP status:
ent.IsNotFound(err)--> 404 Not Foundent.IsConstraintError(err)--> 409 Conflict (duplicate key, FK violation)ent.IsValidationError(err)--> 400 Bad Request- Custom domain errors --> mapped to specific status codes
Never return raw database errors to the client. Always wrap errors with domain context and let the handler map to user-friendly messages.
Adding a New Service
- Create a file in
internal/service/following the naming convention - Define a struct with dependencies as fields
- Create a constructor function (
NewXxxService) - Implement methods with the context pattern:
(ctx context.Context, ...) (..., error) - Wire the service in
cmd/server/main.go - Write unit tests in
internal/service/xxx_test.go
Further Reading
- Backend Development -- Handler-Service-Ent pattern
- Ent Schemas -- Database schema conventions
- Job Queue -- Async job patterns
- Middleware Chain -- Auth and RBAC middleware