Skip to content

Latest commit

 

History

History
469 lines (314 loc) · 6.48 KB

File metadata and controls

469 lines (314 loc) · 6.48 KB

This document is authoritative.

All new code generated by AI tools must follow these rules. If a generated implementation conflicts with this document, the architecture defined here takes precedence.

Signus Backend – Architecture Guide

This document defines the architectural conventions used in this project. All new code should follow these guidelines to ensure consistency, maintainability, and clean architecture principles.

The goal is to keep the codebase:

  • Consistent
  • Decoupled
  • Testable
  • Easy to reason about

This architecture follows ideas from Clean Architecture and Hexagonal Architecture, adapted pragmatically for this project.


1. High-Level Architecture

The project is organized around features, not layers.

core/       -> cross-cutting infrastructure
features/   -> business functionality grouped by feature

Core

Contains application-wide infrastructure:

core/
 ├── config
 ├── database
 ├── di
 ├── exceptions
 ├── plugins
 └── security

Responsibilities:

  • Application configuration
  • Dependency injection
  • Security (JWT, password hashing)
  • Database initialization
  • Framework plugins

Core must not depend on features.


2. Feature-Based Structure

All business logic lives inside features.

Example:

features/
 ├── auth
 ├── linking
 ├── semaphore
 ├── notification
 └── user

Each feature is self-contained.

A typical feature structure:

feature_name/
 ├── dto/
 ├── ports/
 ├── FeatureRoutes.kt
 ├── FeatureServiceImpl.kt
 ├── Entity.kt
 ├── Repository.kt
 └── Table.kt

3. Responsibilities per Component

Routes

File example:

LinkingRoutes.kt
AuthRoutes.kt
StatusRoutes.kt

Responsibilities:

  • HTTP endpoints
  • Request validation
  • Authentication extraction
  • Calling application services
  • Returning HTTP responses

Routes must not contain business logic.


Services

Services implement business logic and use cases.

Naming convention:

FeatureService (interface)
FeatureServiceImpl (implementation)

Example:

ports/LinkingService.kt
LinkingServiceImpl.kt

Services:

  • Orchestrate business logic
  • Call repositories
  • Apply domain rules
  • Throw domain exceptions

Services must not depend on framework details (Ktor, HTTP).


Ports

Ports define interfaces used for dependency inversion.

They live inside:

ports/

Types of ports:

Service Ports

Define the contract used by routes.

Example:

ports/LinkingService.kt
ports/AuthService.kt

Repository Ports

Define persistence operations.

Example:

ports/AuthUserRepositoryPort.kt
ports/LinkSessionRepositoryPort.kt

Services depend on repository ports, not concrete implementations.


4. Repositories

Repositories implement persistence logic.

Example:

LinkSessionRepository.kt
UserRepository.kt
SemaphoreRepository.kt

Responsibilities:

  • Database access
  • Mapping between DB rows and domain models

Repositories implement repository ports.

Example:

class LinkSessionRepository : LinkSessionRepositoryPort

Repositories should contain no business logic.


5. Domain Models

Domain models represent core entities.

Example:

LinkSession.kt
User.kt
Semaphore.kt

They contain:

  • domain data
  • domain states
  • domain invariants (if simple)

They must not contain framework annotations unless strictly necessary.


6. Database Tables

Database schemas are defined using Exposed tables.

Example:

UserTable.kt
LinkSessionTable.kt
SemaphoreTable.kt

Tables should only define:

  • column structure
  • constraints
  • indexes

No business logic.


7. DTOs

DTOs represent data transferred through HTTP.

Located in:

dto/

Naming conventions:

CreateSomethingRequest
CreateSomethingResponse
SomethingStatusResponse

Example:

ConfirmLinkSessionRequest
CreateLinkSessionResponse
LinkSessionStatusResponse

DTOs must be serialization-friendly.

Example:

@Serializable
data class ConfirmLinkSessionRequest(
    val linkCode: String
)

8. DTO Mapping

DTO mapping should not live inside domain models.

Mapping functions may live in:

dto/DtoMappers.kt

Example:

fun toCreateResponse(...)
fun toStatusResponse(...)

This keeps:

  • domain layer clean
  • DTO logic separated

9. Dependency Injection

Dependency injection is handled using Koin.

Defined in:

core/di/KoinModules.kt

Example:

single<LinkingService> { LinkingServiceImpl(get()) }
single<LinkSessionRepositoryPort> { LinkSessionRepository() }

Always inject interfaces, not implementations.

Correct:

LinkingService -> LinkingServiceImpl
LinkSessionRepositoryPort -> LinkSessionRepository

10. Error Handling

Domain errors should use custom exceptions.

Example:

LinkSessionExpiredException
EmailAlreadyExistsException

Routes translate domain exceptions to HTTP responses.

Example:

409 Conflict
401 Unauthorized
410 Gone

11. Feature Independence

Features should not directly depend on each other's implementations.

Communication between features should occur via:

  • service ports
  • domain events
  • orchestrators

Example:

notification/NotificationOrchestrator.kt

12. Naming Conventions

Services

FeatureService
FeatureServiceImpl

Repositories

FeatureRepository
FeatureRepositoryPort

Routes

FeatureRoutes

DTOs

ActionRequest
ActionResponse
SomethingStatusResponse

13. Clean Code Principles

The project follows these principles:

  • Single Responsibility Principle
  • Dependency Inversion
  • Feature modularity
  • Explicit naming
  • Small functions
  • No hidden side effects

Avoid:

  • fat routes
  • business logic in repositories
  • framework leakage into domain code

14. When Adding a New Feature

Use this structure:

features/newfeature/

 ├── dto/
 │   ├── CreateThingRequest.kt
 │   └── CreateThingResponse.kt
 │
 ├── ports/
 │   ├── ThingService.kt
 │   └── ThingRepositoryPort.kt
 │
 ├── ThingRoutes.kt
 ├── ThingServiceImpl.kt
 ├── ThingRepository.kt
 ├── Thing.kt
 └── ThingTable.kt

15. Goals of This Architecture

This architecture aims to:

  • keep the codebase consistent
  • enable easier testing
  • reduce coupling
  • support future growth
  • make the system understandable for humans and AI tools