Skip to content

Application Layer

The Application layer orchestrates domain logic through commands and queries (CQRS pattern). It coordinates between the domain layer and infrastructure layer.

Location

application/
├── commands/          # Command handlers
│   ├── key/
│   ├── text/
│   └── settings/
├── queries/          # Query handlers
│   ├── key/
│   └── settings/
├── common/           # Shared application code
│   ├── ports/        # Interfaces
│   └── views/        # DTOs
└── errors/           # Application errors

Principles

  • Orchestrates domain logic: Coordinates domain services
  • CQRS pattern: Separates commands and queries
  • Interface-based: Uses ports (interfaces) for infrastructure
  • Error handling: Converts domain errors to application errors

Commands

Commands represent write operations:

Key Commands

Generate and Save Key

data class AesGenerateAndSaveKeyCommand(
    val algorithm: EncryptionAlgorithm
)

class AesGenerateAndSaveKeyCommandHandler @Inject constructor(
    private val encryptionService: AesEncryptionService,
    private val keyStorage: KeyCommandGateway
) {
    suspend fun handle(command: AesGenerateAndSaveKeyCommand): Result<KeyView> {
        return try {
            // 1. Generate key using domain service
            val key = encryptionService.generateKey(command.algorithm)

            // 2. Save via infrastructure adapter
            keyStorage.save(key)

            // 3. Convert to view
            Result.success(key.toView())
        } catch (e: Exception) {
            Result.failure(KeyGenerationError(e.message ?: "Unknown error"))
        }
    }
}

Delete Key

data class DeleteKeyCommand(
    val keyId: String
)

class DeleteKeyCommandHandler @Inject constructor(
    private val keyStorage: KeyCommandGateway
) {
    suspend fun handle(command: DeleteKeyCommand): Result<Unit> {
        return try {
            keyStorage.delete(KeyId(command.keyId))
            Result.success(Unit)
        } catch (e: Exception) {
            Result.failure(KeyDeleteError(e.message ?: "Unknown error"))
        }
    }
}

Text Commands

Encrypt Text

data class AesEncryptTextCommand(
    val text: String,
    val keyId: String,
    val algorithm: EncryptionAlgorithm
)

class AesEncryptTextCommandHandler @Inject constructor(
    private val encryptionService: AesEncryptionService,
    private val keyStorage: KeyQueryGateway
) {
    suspend fun handle(command: AesEncryptTextCommand): Result<EncryptedTextView> {
        return try {
            // 1. Get key
            val key = keyStorage.readById(KeyId(command.keyId))
                ?: return Result.failure(KeyNotFoundError("Key not found"))

            // 2. Create text entity
            val text = Text.create(command.text, TextEncoding.UTF8)
                .getOrElse { return Result.failure(it) }

            // 3. Encrypt
            val encrypted = encryptionService.encrypt(text, key)
                .getOrElse { return Result.failure(it) }

            // 4. Convert to view
            Result.success(encrypted.toView())
        } catch (e: Exception) {
            Result.failure(EncryptionError(e.message ?: "Unknown error"))
        }
    }
}

Queries

Queries represent read operations:

Key Queries

Read All Keys

data class ReadAllKeysQuery(
    // No parameters
)

class ReadAllKeysQueryHandler @Inject constructor(
    private val keyStorage: KeyQueryGateway
) {
    suspend fun handle(query: ReadAllKeysQuery): Result<List<KeyView>> {
        return try {
            val keys = keyStorage.readAll()
            val views = keys.map { it.toView() }
            Result.success(views)
        } catch (e: Exception) {
            Result.failure(KeyReadError(e.message ?: "Unknown error"))
        }
    }
}

Views (DTOs)

Views are Data Transfer Objects for presentation:

data class KeyView(
    val id: String,
    val algorithm: String,
    val keyBase64: String
)

data class EncryptedTextView(
    val encryptedBase64: String,
    val ivBase64: String,
    val algorithm: String
)

// Conversion functions
fun EncryptionKey.toView(): KeyView {
    return KeyView(
        id = this.id.value,
        algorithm = this.algorithm.name,
        keyBase64 = Base64.encodeToString(this.keyBytes, Base64.NO_WRAP)
    )
}

Ports (Interfaces)

Ports define contracts for infrastructure adapters:

interface KeyCommandGateway {
    suspend fun save(key: EncryptionKey)
    suspend fun delete(keyId: KeyId)
    suspend fun deleteAll()
}

interface KeyQueryGateway {
    suspend fun readAll(): List<EncryptionKey>
    suspend fun readById(keyId: KeyId): EncryptionKey?
}

Error Handling

Application errors wrap domain errors:

sealed class ApplicationError : AppError {
    data class KeyGenerationError(override val message: String) : ApplicationError()
    data class KeyDeleteError(override val message: String) : ApplicationError()
    data class KeyNotFoundError(override val message: String) : ApplicationError()
    data class EncryptionError(override val message: String) : ApplicationError()
    data class DecryptionError(override val message: String) : ApplicationError()
}

Learn More