71 votes

Le printemps et l'anémie du modèle de domaine

Donc, j'ai remarqué que j'ai certainement une tendance à répéter mon Spring/Hibernate empiler des objets, comme ceci:

  • Foo contrôleur effectue un appel à "FooService"
  • FooService appels FooRepository.getById() méthode pour obtenir certains Foos.
  • FooRepository fait quelques appels à Hibernate pour charger Foo objets.
  • FooService n'certaines interactions avec les Foos. Il peut utiliser une relative TransactionalFooService à gérer les choses qui doivent être faites ensemble dans une transaction.
  • FooService demande FooRepository pour enregistrer les Foos.

Le problème ici est que les Foos n'ont pas de réelle logique. Par exemple, si un courriel doit être envoyé à chaque fois qu'un Toto arrive à expiration, il n'y a pas un appel à la Foo.expirer(). Il y a un appel à FooService.expireFoo(fooId). C'est pour une variété de raisons:

  • C'est gênant pour l'obtenir à d'autres services et d'objets à partir d'un Foo. Ce n'est pas un Printemps bean, et il a été chargé par Hibernate.
  • C'est ennuyeux pour obtenir un truc pour faire plusieurs choses de manière transactionnelle.
  • Il est difficile de décider si Foo devrait être responsable du choix quand à sauver lui-même. Si vous appeler foo.setName(), devrait foo persister le changement? Faut-il attendre jusqu'à ce que vous appeler foo.save()? Devrait foo.save (), il suffit d'invoquer FooRepository.enregistrer(ce)?

Donc, pour ces raisons, mon Printemps des objets du domaine ont tendance à être essentiellement glorifié les structures avec une certaine logique de validation. Peut-être que ce n'est pas grave. Peut-être que les services web sont d'accord que le code de procédure. Peut-être que de nouvelles fonctionnalités sont écrites, il est possible de créer de nouveaux services qui traitent de la même vieux objets dans de nouveaux moyens.

Mais j'aimerais sortir de ce genre de design, et je me demandais ce que les autres Printemps utilise faire à ce sujet? Avez-vous combattre avec fantaisie comme les temps de charge de tissage (dont je ne suis pas très à l'aise)? Avez-vous une autre astuce? Pensez-vous procédurale est bien?

12voto

ptomli Points 5778

Vous pouvez obtenir de Printemps pour injecter de vos services dans votre Hibernate instancié cas, l'utilisation de l'AOP. Vous pouvez également obtenir d'Hibernation pour faire de même, à l'aide d'Intercepteurs.

Voir http://www.jblewitt.com/blog/?p=129

Quant à "C'est ennuyeux pour obtenir un truc pour faire plusieurs choses point de vue transactionnel", je m'attends à votre service des implémentations serait de savoir ou de soins sur les transactions, et si vous êtes maintenant en utilisant les interfaces de service au sein de votre modèle de domaine, qui devrait maintenant être pas tout à fait ennuyeux.

Je soupçonne que décider quand un modèle de domaine doit être enregistré dépend de ce qu'il est et ce que vous faites avec elle.

FWIW, j'ai tendance à produire exactement le même genre de anémique structures, mais je suis là, maintenant, je sais que c'est possible de le faire de manière plus sensible.

10voto

Kevin Swiber Points 965

Il semble que votre application est conçue autour de procédure les principes du codage. Cela ne sera d'entraver toute la programmation orientée objet que vous essayez de faire.

Il est possible qu'un Toto n'a pas de comportement qu'il contrôle. Il est également acceptable de ne pas utiliser un Modèle du Domaine modèle si votre logique métier est minime. Une Transaction Script motif parfois tout à fait logique.

Le problème vient quand cette logique commence à se développer. Refactoring d'une Transaction Script dans un Modèle de Domaine n'est pas la chose la plus facile, mais c'est certainement pas le plus difficile. Si vous avez des tonnes de logique entourant Foo, je vous recommande de passer à la Domaine de modèle de Modèle. L'encapsulation des avantages, il est très facile de comprendre ce qu'il se passe et qui est impliqué avec quoi.

Si vous voulez avoir l' Foo.Expire(), de créer un événement dans votre classe Foo comme OnExpiration. Fil de votre foo.OnExpiration += FooService.ExpireFoo(foo.Id) sur la création de l'objet, éventuellement par l'intermédiaire d'une usine utilisée par l' FooRepository.

Vraiment penser en premier. Il est très possible que tout est déjà en place... pour l'instant.

Bonne chance!

5voto

ArtB Points 3922

Je pense qu'il est un simple modèle qui permettra de résoudre votre problème.

  1. Injecter votre service dans votre référentiel.
  2. Avant de retourner votre Foo ensemble de ses FooService
  3. Maintenant que votre votre FooController demander pour le Foo de la FooRepository
  4. Maintenant appeler les méthodes que vous voulez sur vous Foo. Si elle ne peut pas les mettre en œuvre lui-même, il appelle la méthode appropriée sur le FooService.
  5. Maintenant supprimer tous les appels à la FooService au travers de ce que j'aime appeler "seau pont" méthodes sur Foo (il transmet simplement les paramètres long de la prestation).
  6. À partir de maintenant, chaque fois que vous souhaitez ajouter une méthode ajouter à Toto.
  7. Seulement ajouter des trucs pour le service lorsque vous avez vraiment besoin pour des raisons de performances. Comme toujours, ces méthodes devraient être appelé par le biais du modèle objet.

Cela aidera à évoluer vers une plus riche du modèle de domaine. Il préserve également le Principe de Responsabilité Unique depuis tous vos DB-dépendante code reste dans le FooService implmentations et vous permet de migrer la logique métier de FooService à Toto. Dans vous voulez changer votre back-end pour une autre base de données ou en mémoire ou une maquette (pour les tests), vous n'avez pas besoin de changer quoi que ce soit mais la FooService couche.

^ Je suis en supposant que FooService n'DB appels qui seraient trop lents pour faire à partir d'un ORM comme la sélection de la plus récente Foo qui partage la propriété X avec un Foo. C'est la façon dont la plupart que j'ai vu à l'œuvre.


Exemple

Au lieu de:

class Controller{
    public Response getBestStudentForSchool( Request req ){
        Student bestStudent = StudentService.findBestPupilForSchool( req.getParam( "schlId" ).asInt() );
        ...
    }
}

Vous pourrez évoluer vers quelque chose comme ceci:

class Controller{
    public Response getBestStudentForSchool( Request req ){
        School school = repo.get( School.class, req.getParam( "schlId" ).asInt() ); 
        Student bestStudent = school.getBestStudent();
        ...
    }
}

Qui j'espère vous seront d'accord semble déjà plus riche. Vous êtes maintenant faire un autre à la base de données, mais si vous gardez à l'École mis en cache dans la séance de la pénalité est négligeable. J'ai peur que des OOP modèle sera moins efficace que la faiblesse de modèle que vous utilisez, mais la réduction de bugs dans le code de la clarté devrait être en vaut la peine. Comme toujours, YMMV.

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