Maneje la Evolución del Esquema en IPF Events
A lo largo de la vida de un IPF flow, se realizarán cambios en los eventos que son persistidos por ese flujo. Los cambios en los flujos puede suceder por muchas razones, tales como:
-
Mejor comprensión empresarial del flujo que conduce a un cambio en los requisitos.
-
Las suposiciones en tiempo de diseño no son correctas y, por lo tanto, requieren un cambio en los eventos inicialmente acordados.
-
Razones regulatorias para necesitar capturar más datos en un evento
La naturaleza técnica de estos cambios puede variar, pero generalmente son variantes de los patrones a continuación, que también servirán como subtítulos para esta guía.
Los patrones para la evolución del esquema son:
-
Agregar un campo de datos a un evento
-
Cambiar el nombre de clase completamente calificado (FQCN) de un evento
-
Cambiar el nombre de un elemento de datos en un evento
-
Cambiar el tipo de dato de un elemento en un evento
-
Dividir un evento en múltiples eventos más pequeños
-
Eliminar un evento
El proceso para actualizar eventos está fuera del alcance de este documento. Siga la parte de DSL del tutorial para eventos. definición y modificación.
Estas migraciones de eventos solo deben implementarse si el flujo en sí no está cambiando. Si el flujo en sí también está cambiando además de sus eventos, entonces considere crear una nueva versión del flujo. en conjunto. Consulte la parte de DSL del tutorial para saber cómo definir múltiples versiones de flujos. |
Agregar un campo de datos a un Event
Considere la versión 1 de un evento como sigue:
public class UserRegistered extends DomainEvent {
private final String name;
}
Esto se serializaría en JSON as:
{"name": "jeff"}
Si los nuevos datos que se están añadiendo son simplemente un nuevo campo opcional, como este:
package com.myorg.events;
import java.time. LocalDate;
public class UserRegistered extends DomainEvent {
private final String name;
private final LocalDate dob;
}
La antigua versión serializada seguirá siendo analizada con éxito como un UserRegistered, y no se requiere ningún cambio. Pero, por supuesto, el dob el campo será null.
Sin embargo, las reglas de negocio pueden no permitir el dob el campo debe ser nulo, pero puede haber una fecha de nacimiento de marcador especial
0001-01-01`que puede utilizar para indicar la falta de disponibilidad de una fecha de nacimiento para este usuario. En cuyo caso,
esto `JacksonMigration verificará si dob, y-si está ausente-lo establecerá en el valor predeterminado antes de que sea
deserializado como UserRegistered:
package com.myorg.migrations;
import com.fasterxml.jackson.databind. JsonNode;
import com.fasterxml.jackson.databind.node. ObjectNode;
public class DobFillerMigration extends JacksonMigration {
private static final String DOB_FIELD_NAME = "dob";
@Override
public int currentVersion() {
return 2;
}
@Override
public JsonNode transform(int fromVersion, JsonNode json) {
if (! json.has(DOB_FIELD_NAME)) {
((ObjectNode) json).set(DOB_FIELD_NAME, "0001-01-01");
}
}
}
Y luego vinculado al evento UserRegistered añadiéndolo a la configuración de IPF de la siguiente manera:
akka.serialization.jackson.migrations {
"com.myorg.events. UserRegistered" = "com.myorg.migrations. DobFillerMigration"
}
Recuerde, si el nuevo campo se permite que sea nulo, no es necesario escribir ninguna migración.
Cambiar el nombre de clase completamente calificado (FQCN) de un evento, o renombrar un evento
Esto no puede ser remediado arreglando un JacksonMigration, ya que JacksonMigration trabaja en el cuerpo y no en el tipo.
Sin embargo, si no ha cambiado nada más aparte del nombre de clase completamente calificado (o simplemente el nombre) del evento, y el Icon MongoDB Akka Persistence el plugin está siendo utilizado, esto puede ser remediado utilizando un MongoDB declaración de actualización.
Como ejemplo, si el nombre del paquete de los eventos estaba mal escrito como com.myorg.evnets y queremos corregir el error tipográfico, el
la siguiente instrucción de actualización cambiará el nombre del paquete de todos com.myorg.evnets eventos a com.myorg.events:
const updates = [];
db.journal.find({"eventPayloads.payload.type":/com.iconsolutions.instantpayments/})
.forEach(doc => {
doc.eventPayloads.forEach(pld => {
const oldFQCN = pld.payload.type;
const newFQCN = oldFQCN.replace("com.iconsolutions", "com.monkey");
updates.push({"updateOne": {
"filter": {$and: [{"_id": doc._id}, {"eventPayloads.payload.type":oldFQCN}]},
"update": {$set: {"eventPayloads.$.payload.type": newFQCN}}
}})
})
});
print(updates);
// uncomment after this line to actually run the update
// const result = db.journal.bulkWrite(updates);
// print(JSON.stringify(result));
Algo a tener en cuenta sobre el fragmento anterior:
-
El nombre predeterminado del diario es
journalpero esto puede ser anulado coniconsolutions.akka.persistence.mongodb.journal-collection
Cambio del Nombre de un Elemento de Datos en un Event
Esto también puede resolverse escribiendo un JacksonMigration.
Considere la v1 del evento como:
import java.time. LocalDate;
public class UserRegistered extends DomainEvent {
private final String name;
private final LocalDate dob;
}
Esto se serializaría en JSON as:
{"name": "jeff", "dob": "1985-01-01"}
Pero usted decide que dob puede que no sea tan claro y decidir renombrarlo a dateOfBirth:
package com.myorg.events;
import java.time. LocalDate;
public class UserRegistered extends DomainEvent {
private final String name;
private final LocalDate dateOfBirth;
}
Escriba la siguiente migración para renombrar dob to dateOfBirth:
package com.myorg.migrations;
import com.fasterxml.jackson.databind. JsonNode;
import com.fasterxml.jackson.databind.node. ObjectNode;
public class DobRenameMigration extends JacksonMigration {
private static final String OLD_FIELD_NAME = "dob";
private static final String NEW_FIELD_NAME = "dateOfBirth";
@Override
public int currentVersion() {
return 2;
}
@Override
public JsonNode transform(int fromVersion, JsonNode json) {
if (json.has(OLD_FIELD_NAME)) {
//get value of dob
var seqValue = json.get(OLD_FIELD_NAME);
//set it to new field
((ObjectNode) json).set(NEW_FIELD_NAME, seqValue);
//remove old field
((ObjectNode) json).remove(OLD_FIELD_NAME);
}
return json;
}
}
Y luego vinculado al evento UserRegistered al añadirlo a la configuración de IPF de la siguiente manera:
akka.serialization.jackson.migrations {
"com.myorg.events. UserRegistered" = "com.myorg.migrations. DobRenameMigration"
}
Cambiando el Data Type de un Elemento en un Event
Cambiar un tipo puede significar múltiples cosas, tales como:
-
Un elemento de datos que se divide en múltiples elementos
-
Pasando de un tipo simple a un tipo complejo
Pero ambos se manejan de manera más o menos similar, que consiste en escribir un JacksonMigration para mapear los valores del antiguo
versión del evento a su nueva representación.
Continuando con el ejemplo anterior del evento v1:
package com.myorg.events;
import java.time. LocalDate;
public class UserRegistered extends DomainEvent {
private final String name;
private final LocalDate dateOfBirth;
}
Imaginemos que la empresa desea devolver los detalles del nombre en un separado Name objeto:
package com.myorg.model;
import java.time. LocalDate;
public class Name {
private final String firstName;
private final String middleName;
private final String lastName;
}
Así que el nuevo evento se ve así:
package com.myorg.events;
import com.myorg.model. Name;
import java.time. LocalDate;
public class UserRegistered extends DomainEvent {
private final Name name;
private final LocalDate dateOfBirth;
}
Suponiendo la siguiente regla de migración imaginaria del negocio:
-
Si un nombre contiene dos tokens, divídalo en nombre y apellido en ese orden.
-
Si un nombre contiene tres tokens, divídalo en nombre, segundo nombre y apellido en ese orden.
| ¡Esta no es una buena manera de descomponer el nombre de alguien en partes constitutivas! |
Bajo estas reglas, la migración sería:
package com.myorg.migrations;
import com.fasterxml.jackson.databind. JsonNode;
import com.fasterxml.jackson.databind.node. JsonNodeFactory;import com.fasterxml.jackson.databind.node. ObjectNode;
public class NameMigration extends JacksonMigration {
@Override
public int currentVersion() {
return 2;
}
@Override
public JsonNode transform(int fromVersion, JsonNode json) {
var name = json.get("name").asText();
var nameNode = JsonNodeFactory.instance.objectNode();
var nameSplit = name.split("\s");
if (nameSplit.length == 2) {
nameNode.set("firstName", nameSplit[0]);
nameNode.set("lastName", nameSplit[1]);
} else if (nameSplit.length == 3) {
nameNode.set("firstName", nameSplit[0]);
nameNode.set("middleName", nameSplit[1]);
nameNode.set("lastName", nameSplit[2]);
}
((ObjectNode) json).set("name", nameNode);
return json;
}
}
Y en la configuración de IPF:
akka.serialization.jackson.migrations {
"com.myorg.events. UserRegistered" = "com.myorg.migrations. NameMigration"
}
Dividir un Event En múltiples, más pequeñas Events
Esto puede ser implementado utilizando un EventAdapter. El enfoque es similar a la redacción de una JacksonMigration, pero es
implementado después de que los eventos hayan sido deserializados.
Consulte el Akka documentos para más información sobre este tema:https://doc.akka.io/docs/akka/current/persistence-schema-evolution.html#split-large-event-into-fine-grained-events[Divida el evento grande en eventos de menor granularidad.].
Para implementar un EventAdapter que se pasará a la EventSourcedBehaviour para el flujo, al iniciar el dominio,
utilizar withBehaviourExtensions para suministrar un BehaviourExtensions implementación como esta:
import java.util. Optional;
public class MyBehaviourExtensions implements BehaviourExtensions {
@Override
public Optional<EventAdapter<Event, Event>> eventAdapter() {
return Optional.of(new EventAdapter<>() {
@Override
public Event toJournal(Event event) {
return event;
}
@Override
public String manifest(Event event) {
return "";
}
@Override
public EventSeq<Event> fromJournal(Event event, String manifest) {
//if it's MySuperEvent, devolve it into two smaller events
if (event instanceof MySuperEvent) {
var mse = (MySuperEvent) event;
return EventSeq.create(List.of(
new MySmallerEvent1(mse.data1()),
new MySmallerEvent2(mse.data2())
));
}
//otherwise return any other event as-is
return EventSeq.single(event);
}
});
}
}
y luego..
@EventListener
public void init(ContextRefreshedEvent event, MyBehaviourExtensions myBehaviourExtensions) {
new MyDomain. Builder(actorSystem)
.withEventBus(eventBus)
.withSchedulerAdapter(schedulerAdapter)
.withSystemAActionAdapter(new SampleSanctionsActionAdapter(sanctionsAdapter))
.withSystemBActionAdapter(new SampleSanctionsActionAdapter(sanctionsAdapter))
.withBehaviourExtensions(myBehaviourExtensions) (1)
.build();
}
| 1 | Cómo añadir la implementación de BehaviourExtensions |
Eliminando un Event
Esto es lo mismo que arriba, pero en su lugar emita un EventSeq.empty(el resto de la implementación es el mismo):
@Override
public EventSeq<Event> fromJournal(Event event, String manifest) {
//if it's MyUnwantedEvent, pretend we didn't see it
if (event instanceof MyUnwantedEvent) {
return EventSeq.empty();
}
//otherwise return any other event as-is
return EventSeq.single(event);
}