scala - por - ddd software
Programación Funcional+Diseño Dirigido por Dominio (2)
La programación funcional promueve clases inmutables y transparencia referencial.
El diseño impulsado por el dominio se compone de Objeto de valor (inmutable) y Entidades (mutables).
¿Debemos crear Entidades inmutables en lugar de mutables?
Supongamos que el proyecto usa Scala como idioma principal. ¿Cómo podríamos escribir Entidades como clases de casos (inmutables) sin arriesgarnos a un estado obsoleto si nos enfrentamos a la concurrencia?
¿Qué es una buena práctica? ¿Mantener a las entidades mutables (campos var
, etc ...) y evitar la gran sintaxis de las clases de casos ?
Esta es una especie de pregunta de opinión que es menos específica de lo que usted cree.
Si realmente quieres adoptar FP, iría por la ruta inmutable de todos tus objetos de dominio y nunca los pondré en práctica.
Es decir, algunas personas llaman al patrón de servicio anterior donde siempre hay una separación entre el comportamiento y el estado. Esto se evitó en OOP pero natural en FP.
También depende de cuál sea tu dominio. OOP es algunas veces más fácil con cosas con estado como la interfaz de usuario y los videojuegos. Para servicios de back-end de núcleo duro como sitios web o REST Creo que el patrón de servicio es mejor.
Dos cosas realmente agradables que me gustan de los objetos inmutables además de la concurrencia mencionada a menudo es que son mucho más confiables para almacenar en caché y también son excelentes para el paso de mensajes distribuidos (por ejemplo, protobuf sobre amqp) ya que la intención es muy clara.
También en FP, las personas combaten lo mutable a lo inmutable creando un "lenguaje" o "diálogo", también conocido como DSL ( Builders , Mónadas, Tuberías, Flechas, STM , etc.) que le permite mutar y luego volver a transformarse en lo inmutable. dominio. Los servicios mencionados anteriormente utilizan el DSL para realizar cambios. Esto es más natural de lo que piensa (por ejemplo, SQL es un ejemplo de "diálogo"). Por otro lado, OOP prefiere tener un dominio mutable y aprovechar la parte procesal existente del lenguaje.
Puede usar efectivamente Entidades inmutables en Scala y evitar el horror de los campos mutables y todos los errores que se derivan del estado mutable. El uso de entidades inmutables lo ayuda con la concurrencia, no empeora las cosas. Su estado mutable anterior se convertirá en un conjunto de transformación que creará una nueva referencia en cada cambio.
Sin embargo, en un cierto nivel de su aplicación, deberá tener un estado mutable, o su aplicación sería inútil. La idea es empujarlo tan arriba como puedas en la lógica de tu programa. Tomemos un ejemplo de una cuenta bancaria, que puede cambiar debido a la tasa de interés y al retiro o depósito en cajeros automáticos.
Tienes dos enfoques válidos:
Expone métodos que pueden modificar una propiedad interna y administra la concurrencia en esos métodos (muy pocos, de hecho)
Usted hace que toda la clase sea inmutable y la rodea con un "administrador" que puede cambiar la cuenta.
Como el primero es bastante sencillo, detallaré el primero.
case class BankAccount(val balance:Double, val code:Int)
class BankAccountRef(private var bankAccount:BankAccount){
def withdraw(withdrawal) = {
bankAccount = bankAccount.copy(balance = bankAccount.balance - withdrawal)
bankAccount.balance
}
}
Esto es bueno, pero, Dios mío, todavía estás atascado con la gestión de la concurrencia. Bueno, Scala te ofrece una solución para eso. El problema aquí es que si comparte su referencia a BankAccountRef a su trabajo en segundo plano, tendrá que sincronizar la llamada. El problema es que estás haciendo concurrencia de una manera subóptima.
La forma óptima de hacer concurrencia: paso de mensajes.
¿Qué sucede si, por otro lado, los diferentes trabajos no pueden invocar métodos directamente en BankAccount o en BankAccountRef, pero solo notificarles que se deben realizar algunas operaciones? Bueno, entonces tienes un Actor, la forma favorita de hacer concurrencia en Scala.
class BankAccountActor(private var bankAccount:BankAccount) extends Actor {
def receive {
case BalanceRequest => sender ! Balance(bankAccount.balance)
case Withdraw(amount) => {
this.bankAccount = bankAccount.copy(balance = bankAccount.balance - amount)
}
case Deposit(amount) => {
this.bankAccount = bankAccount.copy(balance = bankAccount.balance + amount)
}
}
}
Esta solución se describe ampliamente en la documentación de Akka: http://doc.akka.io/docs/akka/2.1.0/scala/actors.html . La idea es que se comunique con un Actor enviando mensajes a su buzón, y esos mensajes se procesan por orden de recepción. Como tal, nunca tendrá fallas de concurrencia si usa este modelo.