3 votes

Conversion automatisée entre les objets commerciaux immuables et les messages MessagePack

En Java, j'aimerais utiliser des hiérarchies de POJO immuables pour exprimer mon modèle de domaine.

par exemple

final ServiceId id = new ServiceId(ServiceType.Foo, "my-foo-service")
final ServiceConfig cfg = new ServiceConfig("localhost", 8080, "abc", JvmConfig.DEFAULT)
final ServiceInfo info = new ServiceInfo(id, cfg)

Tous ces POJO ont des champs publics finaux sans getters ni setters. (Si vous êtes un fan des getters, prétendez que les champs sont privés avec des getters).

J'aimerais également sérialiser ces objets à l'aide de la fonction MessagePack afin de les faire circuler sur le réseau, de les stocker sur des nœuds ZooKeeper, etc.

Le problème est que MessagePack ne prend en charge que la sérialisation des champs publics, non finaux, de sorte que je ne peux pas sérialiser les objets commerciaux tels quels. De plus, MessagePack ne prend pas en charge enum Je dois donc convertir les valeurs de l'énumération en int o String pour la sérialisation. (Oui, c'est le cas, si vous ajoutez une annotation à votre enum s. Voir mon commentaire ci-dessous).

Pour y remédier, j'ai rédigé à la main une hiérarchie correspondante d'objets "messages", avec des conversions entre chaque objet métier et son objet message correspondant. Il est évident que cette méthode n'est pas idéale, car elle entraîne une grande quantité de code dupliqué, et une erreur humaine peut entraîner des champs manquants, etc.

Existe-t-il de meilleures solutions à ce problème ?

  • Génération de code au moment de la compilation ?
  • Comment générer les classes sérialisables appropriées au moment de l'exécution ?
  • Abandonner MessagePack ?
  • Renoncer à l'immutabilité et enum dans mes objets commerciaux ?
  • Existe-t-il une sorte de bibliothèque générique capable d'envelopper un objet mutable (l'objet message) dans un objet immuable (l'objet métier) ?

MessagePack prend également en charge la sérialisation des Java Beans (en utilisant l'annotation @MessagePackBeans), de sorte que si je peux convertir automatiquement un objet immuable vers/depuis un Java Bean, cela pourrait me rapprocher d'une solution.

1voto

cambecc Points 1490

Par coïncidence, j'ai récemment créé un projet qui fait à peu près exactement ce que vous décrivez. L'utilisation de modèles de données immuables offre d'énormes avantages, mais de nombreuses technologies de sérialisation semblent s'approcher de l l'immutabilité comme une réflexion après coup. Je voulais quelque chose qui corrige ce problème.

Mon projet, Céréales utilise la génération de code pour créer une implémentation immuable d'un modèle de domaine. L'implémentation est suffisamment générique pour être adaptée à différents cadres de sérialisation. MessagePack, Jackson, Kryo et la sérialisation Java standard sont pris en charge jusqu'à présent.

Il suffit d'écrire un ensemble d'interfaces qui décrivent votre modèle de domaine. Par exemple, il suffit d'écrire un ensemble d'interfaces qui décrivent votre modèle de domaine :

public interface ServiceId {
    enum ServiceType {Foo, Bar}

    String getName();
    ServiceType getType();
}

public interface ServiceConfig {
    enum JvmConfig {DEFAULT, SPECIAL}

    String getHost();
    int getPort();
    String getUser();
    JvmConfig getType();
}

public interface ServiceInfo {
    ServiceId getId();
    ServiceConfig getConfig();
}

En Céréales Le plugin Maven génère alors des implémentations immuables de ces interfaces au moment de la compilation. (Le code source qu'il génère est conçu pour être lu par des humains.) Vous créez ensuite des instances de vos objets. Cet exemple montre deux modèles de construction :

ServiceIdGrain id = ServiceIdFactory.defaultValue()
    .withType(ServiceType.Foo)
    .withName("my-foo-service");

ServiceConfigBuilder cfg = ServiceConfigFactory.newBuilder()
    .setHost("localhost")
    .setPort(8080)
    .setUser("abc")
    .setType(JvmConfig.DEFAULT);

ServiceInfoGrain info = ServiceInfoFactory.defaultValue()
    .withId(id)
    .withConfig(cfg.build());

Pas aussi simple que votre public final Je sais, mais l'héritage et la composition ne sont pas possibles sans getters. et setters. De plus, ces objets sont faciles à lire et à écrire avec MessagePack :

MessagePack msgpack = MessagePackTools.newGrainsMessagePack();

byte[] data = msgpack.write(info);
ServiceInfoGrain unpacked = msgpack.read(data, ServiceInfoGrain.class);

Si le Céréales ne fonctionne pas pour vous, n'hésitez pas à inspecter son Modèles de MessagePack . Vous pouvez écrire un TemplateBuilder qui utilise la réflexion pour définir les champs finaux de votre modèle de domaine écrit à la main. L'astuce est de créer un TemplateRegistry qui permet l'enregistrement de votre constructeur personnalisé.

0voto

Engineer Dollery Points 3149

Il semble que vous ayez fusionné, plutôt que séparé, les préoccupations de lecture et d'écriture de votre application. Vous devriez probablement envisager le CQRS à ce stade.

D'après mon expérience, les objets de domaine immuables sont presque toujours liés à une histoire d'audit (exigence) ou à ses données de consultation (énumérations).

Votre domaine devrait probablement être, en grande partie, mutable, mais vous n'avez toujours pas besoin de getters et de setters. Au lieu de cela, vous devriez avoir des verbes sur vos objets qui résultent en un modèle de domaine modifié, et qui déclenchent des événements lorsque quelque chose d'intéressant se produit dans le domaine (intéressant pour l'entreprise -- entreprise == quelqu'un qui paie pour votre temps). Ce sont probablement les événements qui vous intéressent, et non les objets du domaine. Peut-être est-ce même les commandes (celles-ci sont similaires aux événements, mais la source est un agent externe au contexte délimité dans lequel vit votre domaine -- les événements sont internes au contexte délimité du modèle).

Vous pouvez avoir un service pour conserver les événements (et un autre pour conserver les commandes), qui est également votre journal d'audit (remplissant vos histoires d'audit).

Vous pouvez avoir un gestionnaire d'événements qui transmet vos événements à votre bus. Ces événements doivent contenir des informations simples ou des identifiants d'entités. Les services qui répondent à ces événements doivent exécuter leurs tâches en utilisant les informations fournies, ou ils doivent rechercher les informations dont ils ont besoin en utilisant les identifiants donnés.

Vous ne devriez pas exposer l'état interne de votre modèle de domaine. Vous brisez l'encapsulation en faisant cela, et ce n'est pas vraiment une chose souhaitable à faire. Si j'étais vous, je jetterais un coup d'œil à la section Cadre d'Axon . Il vous permettra probablement d'aller plus loin que MessagePack seul.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X