3 votes

UnitOfWork (NHibernate), un seul UoW/session actif à la fois ? (besoin de conseils)

J'utilise NHibernate, DI/IoC et le modèle de l'unité de travail.

La plupart des exemples de travail non rémunéré que j'ai vus garantissent qu'il ne peut y avoir qu'un seul travail non rémunéré/session actif en même temps, par exemple celui-ci y celui-ci .

Malheureusement, je ne comprends pas encore très bien comment je dois gérer deux services qui utilisent tous deux l'UoW, mais dont l'un appelle l'autre.
Prenons l'exemple suivant :

Un logger qui utilise un UoW :

public class LoggerService : ILoggerService
{
    private ILoggerRepository repo;

    public LoggerService(ILoggerRepository Repo)
    {
        this.repo = Repo;
    }

    public void WriteLog(string message)
    {
        using (UnitOfWork.Start())
        {
            // write log
        }
    }
}

...et un autre service qui utilise également un UoW ET qui appelle l'enregistreur :

public class PlaceOrderService : IPlaceOrderService
{
    private IOrderRepository repo;
    private ILoggerService service;

    public PlaceOrderService(IOrderRepository Repo, ILoggerService Service)
    {
        this.repo = Repo;
        this.service = Service;
    }

    public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            // do stuff

            this.service.WriteLog("Order placed!");  // will throw exception!!

            // do more stuff
        }
    }
}

Si mon implémentation de l'UoW s'assure qu'il n'y a qu'un seul UoW actif en même temps (et lève une exception si vous essayez d'en démarrer un autre, comme dans les deux exemples cités), mon code se plantera dans la section this.service.WriteLog dans la méthode PlaceOrder :
La méthode PlaceOrder a déjà créé un deuxième objet utilitaire actif et la méthode WriteLog tente d'en ouvrir un second, de sorte que l'implémentation de l'objet utilitaire lève une exception à ce sujet.

Alors, que puis-je faire ?
J'ai trouvé deux idées, mais toutes deux me semblent quelque peu "bidon".

  1. Ne pas démarrer un nouvel UoW dans le LoggerService, mais supposer qu'il y en a déjà un actif dans le code appelant.
    C'est ce que je fais en ce moment. Je viens de supprimer le using (UnitOfWork.Start()) à partir du LoggerService et s'assurer que l'on ne peut pas appeler directement le LoggerService, mais seulement à partir d'autres services.
    Cela signifie que le code ci-dessus fonctionnera, mais que le LoggerService se plantera si le code appelant ne démarre pas un UoW, car le LoggerService suppose qu'il en existe déjà un.

  2. Laissez le code de l'exemple tel quel, mais modifiez l'implémentation de UoW.Start() comme suit :
    a) s'il n'y a pas d'OMU actif, en créer un nouveau
    b) s'il existe déjà est un CGU actif, renvoyer celui-ci
    Cela me permettrait d'appeler le LoggerService directement ET à partir d'autres services, qu'il y ait déjà un UoW ou non.
    Mais je n'ai jamais rien vu de tel dans aucun exemple sur le net.

(Et dans cet exemple, il n'y a que deux services. Cela pourrait être beaucoup plus compliqué, il suffit de penser à un PlaceSpecialOrderService qui effectue quelques opérations spéciales et appelle ensuite PlaceOrderService.PlaceOrder() ...)

Des conseils ?
Merci d'avance !


EDIT :

Merci pour les réponses apportées jusqu'à présent.

D'accord, l'exploitation forestière n'était peut-être pas le meilleur exemple.
Je comprends votre point de vue concernant l'utilisation d'une session séparée pour la journalisation, et je vais y jeter un coup d'œil et l'essayer.

Quoi qu'il en soit, je dois encore trouver un moyen de faire fonctionner les appels de service imbriqués.
Imaginez un autre exemple au lieu de l'enregistrement, comme l'exemple PlaceSpecialOrderService que j'ai mentionné plus haut.

Aux personnes qui ont répondu à la question de savoir si je devais commencer mon travail non rémunéré quelque part dans l'infrastructure, et pas directement dans les services, j'ai répondu par l'affirmative :
D'un côté, c'est logique, mais d'un autre côté, cela signifierait évidemment que je ne peux pas effectuer deux transactions différentes en un seul appel de service.
Il faudra que j'y réfléchisse, car je suis certain que j'en aurai besoin quelque part (par exemple : enregistrer une commande dans une transaction, puis faire d'autres choses dans une seconde transaction, et même si cela échoue, la commande n'est pas annulée).

Avez-vous procédé de cette manière (un utilisateur par appel de service) dans vos applications ?
N'avez-vous jamais eu besoin de la possibilité de lancer un deuxième UoW dans le même appel de service ?

3voto

Ladislav Mrnka Points 218632

Je pense que vous avez trouvé un cas très particulier dans lequel vous ne devriez jamais utiliser la même session pour le service commercial et le service d'enregistrement. L'unité de travail est la "transaction commerciale", mais la journalisation ne fait manifestement pas partie de la transaction. Si votre logique métier lève une exception, vos logs seront annulés !!! Utilisez une session séparée pour la journalisation (avec une chaîne de connexion séparée qui limite l'inscription dans la transaction en cours).

2voto

La construction et la gestion de la durée de vie de l'UOW ne doivent pas être l'affaire du service qui met en œuvre votre logique commerciale. Vous devez plutôt configurer votre infrastructure pour qu'elle assure la gestion de l'UOW. En fonction de votre application, vous pouvez avoir un UOW par requête http ou par appel d'opération WCF, ou MessageModule ( think NserviceBus).

La plupart des conteneurs DI disposent déjà d'un support permettant d'associer la durée de vie d'une instance aux contextes susmentionnés.

En ce qui concerne la journalisation, il s'agit dans la plupart des cas d'un problème d'infrastructure et le fait d'avoir un service de journalisation à côté d'un service de traitement des commandes n'est pas une bonne chose. Laissez log4net ou nlog ou ce que vous préférez faire ce pour quoi ils sont conçus.

2voto

zszep Points 1145

Personnellement, je ne pense pas que le fait d'écrire l'état de la commande soit une préoccupation en matière d'enregistrement. Pour moi, il s'agit d'une préoccupation commerciale, donc je remanierais votre service de commande en quelque chose comme ceci :

public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            repository.SaveOrder(order)

            repository.SaveOrderStatus(order,"Order placed")

        }
    }

Et le service de journalisation que j'utiliserais pour les exécutions non gérées, les problèmes d'authentification, etc.

1voto

Christian Specht Points 15907

Je pense avoir trouvé une solution moi-même.

En fait, c'était l'une des solutions que je proposais dans ma question :

Laissez le code de l'exemple tel quel, mais modifiez l'implémentation de UoW.Start() comme suit :
a) s'il n'y a pas d'OMU actif, en créer un nouveau
b) s'il y a déjà un CGU actif, renvoyer celui-ci
Cela me permettrait d'appeler le LoggerService directement ET à partir d'autres services, qu'il y ait déjà un UoW ou non.
Mais je n'ai jamais rien vu de tel dans aucun exemple sur le net.

J'ai déjà eu cette idée avant de poser ma question ici, mais je n'étais pas sûr que ce soit une bonne solution, parce que j'ai trouvé des tas d'implémentations différentes de l'UoW sur le net, mais rien de similaire à mon idée.

Mais j'ai trouvé une implémentation de ceci - j'ai lu le post en entier mais j'ai juste sur-lu la partie pertinente :-)
C'était en le premier lien que j'ai posté dans ma question initiale .
L'implémentation de l'unité de travail possède un champ bool "isRootUnitOfWork".
Le constructeur fait essentiellement ceci (simplifié) :

if (HasActiveSession)
{
    isRootUnitOfWork = false;
    session = GetActiveSession();
}
else
{
    isRootUnitOfWork = true;
    session = CreateSession();
}

Je pense que c'est la solution la plus souple. Je peux appeler un seul de mes services, ou faire en sorte que l'un d'entre eux appelle l'autre... et tout fonctionne, sans qu'il soit nécessaire de recourir à des astuces particulières avec l'UoW.

0voto

Jens Schauder Points 23468

Je recommande de commencer et d'arrêter votre unité de travail en dehors des services. Je ne connais pas les outils appropriés pour le monde .net, mais vous devriez chercher des outils de programmation orientés vers l'aspect. Ou créez manuellement une classe d'enveloppe pour chaque service qui ne fait que démarrer l'unité de travail, puis délègue au vrai service et enfin ferme l'unité de travail. Si un service en appelle un autre, il utilise l'implémentation réelle et non la classe enveloppante de l'unité de travail.

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