====== Patrones y Prácticas de Diseño en PAE ======
===== Patrones arquitectónicos =====
==== Clean Architecture ====
El proyecto sigue Clean Architecture con tres anillos:
- **Entities** (MachineData/RutaPAEData)
* Modelos de negocio
* Independientes de frameworks
* Lógica de negocio local
- **Use Cases** (MachineDomain/RutaPAEDomain)
* Coordinación de flujos
* Implementación de reglas
* Orquestación
- **Frameworks & Drivers** (Machine/RutaPAE)
* UI
* Bases de datos
* APIs externas
==== Hexagonal Architecture (Ports & Adapters) ====
* **Puerto**: ''P2PManager'', ''Hardware'', ''Camera2Service''
* **Adaptador**: ''DirectLink'', ''ScaleManager'', ''Camera2Service''
* El core (dominio) no depende de estos
===== Patrones de diseño =====
==== State Pattern ====
**Ubicación**: ''MachineDomain/state/StateManager.kt''
Estado: interfaz común
├── WaitingForWeight
├── CaptureImages
├── CaptureFace
├── ComparingWeights
├── GenerateEmbedding
├── VerifyInDatabase
├── SaveDelivery
└── WaitForWeightRemoved
StateManager: gestor que ejecuta transiciones
**Ventajas**:
* Flujo lineal y predecible
* Fácil agregar nuevos estados
* Manejo de errores centralizado
==== Repository Pattern ====
**Ubicación**: ''MachineData/repository/'', ''RutaPAEData/repository/''
Repository (contrato)
├── MachineData.Repository
│ ├── DeliveryService
│ ├── BeneficiaryService
│ └── MachineEnrollmentService
│
└── RutaPAEData.Repository
├── DeliveryService
└── MachineService
**Ventajas**:
* Abstrae acceso a datos
* Facilita testing (mock)
* Cambiar BD sin afectar lógica
==== Observer Pattern ====
**Implementación**: Kotlin ''Flow'' y Emisores
// Machine: emite cambios de estado
StateNameEmitter.emit("CapturingFace")
// RutaPAE: emite progreso de sincronización
DeliverySyncUiEmitter.update(estado)
// UI observa
LaunchedEffect {
StateNameEmitter.collect { nombre ->
// actualizar UI
}
}
**Ventajas**:
* Desacoplamiento entre productores y consumidores
* Reactividad
* Múltiples observadores
==== Factory Pattern ====
**StateManager**: crea y ejecuta estados
val states = listOf(
WaitingForWeight(),
CaptureImages(),
CaptureFace(),
// ...
)
while (isRunning) {
val state = workflow[currentIndex] // factory
state.run(next, retry)
}
**Ventajas**:
* Creación centralizada
* Configuración flexible
* Fácil agregar variantes
==== Singleton Pattern ====
**DomainManager** en RutaPAE:
object DomainManager : Initializer, Closeable {
var p2pManager: P2PManager? = null
fun init(context: Context): Boolean {
if (initialized) return true
// ...
}
}
**Ventajas**:
* Instancia única global
* Control centralizado
* Thread-safe
==== Builder Pattern (implicit) ====
**Contract**: construcción de modelos
data class P2PMachineState(
val id: String = "",
val name: String = "",
val unsyncedDeliveries: Long = 0,
// ...
)
// Creación flexible
P2PMachineState(
id = "123",
name = "Máquina 1"
)
===== Principios SOLID =====
==== Single Responsibility ====
StateManager: solo orquesta máquina de estados
DeliveryService: solo gestiona entregas
BeneficiaryService: solo gestiona beneficiarios
==== Open/Closed ====
// Abierto para extensión
interface State {
suspend fun run(next: () -> Unit, retry: (String) -> Unit)
}
// Cerrado para modificación
class NewState : State { ... }
==== Liskov Substitution ====
// Cualquier State puede ocupar el lugar de otro
val states: List = listOf(
WaitingForWeight(),
CaptureImages(),
// ...
)
==== Interface Segregation ====
// Interfaces pequeñas y específicas
interface Initializer {
fun init(context: Context): Boolean
}
interface Closeable {
fun close(context: Context): Boolean
}
// vs una sola interfaz grande
==== Dependency Inversion ====
// Domain depende de abstracción (Repository)
class StateManager(val repository: Repository)
// Data implementa la abstracción
class RepositoryImpl : Repository
===== Patrones de concurrencia =====
==== Coroutines ====
**Machine Domain**:
// State.run() ejecuta en contexto de corrutina
suspend fun run(next: () -> Unit, retry: (String) -> Unit)
**RutaPAE Domain**:
// P2PManager usa corrutinas para operaciones P2P
suspend fun syncDeliveriesFromMachine(machineDatabaseId: Long)
==== StateFlow ====
// P2PManager mantiene state reactivo
val machines: StateFlow>
val peers: StateFlow>
// UI observa
LaunchedEffect {
machines.collect { peers ->
// actualizar UI
}
}
===== Patrones de persistencia =====
==== VectorDB ORM ====
@DataTable("Beneficiary")
data class Beneficiary(
@VectorColumn(dimensions = 512, distanceMetric = DistanceMetric.COSINE)
val embedding: FloatArray,
// ...
)
// Búsqueda vectorial
repository.beneficiaries
.nearestNeighbors(embeddings, queryVector)
==== Repository + Service ====
// Data layer abstraction
class Repository {
val beneficiaries: Table
val deliveries: Table
}
// Service expone API
object BeneficiaryService {
fun findSimilar(embedding: FloatArray): List
}
===== Patrones de error handling =====
==== Retry en State ====
try {
captureImage()
next() // éxito
} catch (e: Exception) {
retry("Error capturando imagen: ${e.message}")
// retrocede a estado anterior
}
==== Result Type ====
suspend fun syncDeliveriesFromMachine(): SyncDeliveriesResult {
return SyncDeliveriesResult(
success = true,
message = "Completado",
fetched = 10,
failed = 0
)
}
==== WorkManager Retry ====
// WorkManager reintentatrabajos automáticamente
Result.retry() // reintentar
Result.failure() // fallar
Result.success() // éxito
===== Patrones de comunicación =====
==== Event-driven ====
**Machine**:
StateNameEmitter.emit(stateName) // event
StateMessageEmitter.emit(message) // event
**RutaPAE**:
DeliverySyncUiEmitter.update(state) // event
SyncRunningEmitter.emit(running) // event
==== Request-Reply ====
**P2P**:
// Machine recibe GET /p2p/machine/deliveries/1/1
// responde con P2PDeliveriesPageResponse
==== Publisher-Subscriber ====
// StateNameEmitter (publicador)
Flow (suscriptores)
// detalles: 1 a N
// desacoplamiento: productor no conoce suscriptores
===== Prácticas de testing (implícitas) =====
==== Dependency Injection ====
// Facilita mock
class StateManager(
val repository: Repository, // inyectable
val hardware: Hardware // inyectable
)
==== Separación de responsabilidades ====
// Fácil probar cada servicio
DeliveryService.getAll() // no depende de UI
BeneficiaryService.findSimilar() // no depende de P2P
==== Contrato compartido ====
// Contract proporciona modelos testables
data class P2PMachineState(...) // serializable, comparable
===== Convenciones de código =====
==== Nomenclatura ====
// Services
class MachineService { }
class DeliveryService { }
// Managers
class StateManager { }
class P2PManager { }
class ScaleManager { }
// Emitters
object StateNameEmitter { }
object DeliverySyncUiEmitter { }
// Interfaces
interface State { }
interface Repository { }
interface Initializer { }
// Data classes
data class Delivery(...) { }
data class P2PMachineState(...) { }
==== Estructura de paquetes ====
co.ada.paemachine/
├── screens/ # UI Composable
├── viewmodels/ # State holders
├── di/ # Inyección
└── ...
co.ada.domain/
├── state/ # State pattern
├── services/ # Business logic
├── hardware/ # Hardware abstractions
└── ...
co.ada.data/
├── entities/ # Data models
├── repository/ # Repository pattern
└── services/ # Data access
===== Code style =====
* **Lenguaje**: Kotlin 100%
* **IDE**: Android Studio
* **Formatter**: Kotlin conventions
* **Compile target**: Java 17
* **Null safety**: Non-null by default