2 votes

Quelle partie du modèle devrait gérer les inserts dans la base de données?

Le titre ne décrit probablement pas très bien mon problème, je serais reconnaissant si quelqu'un pouvait le modifier pour quelque chose de plus approprié. Quoi qu'il en soit:

J'ai un composant censé retourner le prix d'un produit en fonction de son id. Il implémente une interface comme celle-ci:

interface IProductPriceFetcher
{
    double GetPrice(int id);
}

Maintenant, le prix peut être récupéré à partir de 3 sources différentes:

  • service web
  • directement à partir du code source du site web (scrapping)
  • en dernier recours (si le service web et le site web ne sont pas accessibles), le prix le plus récent de la base de données locale est renvoyé

Pour jouer avec cette problématique des 3 sources différentes, j'ai implémenté une classe comme ceci:

class MainFetcher : IProductPriceFetcher
{
    public double GetPrice(int id)
    {
        var priceFetcher = this.factory.GetWebServiceFetcher()
                        ?? this.factory.GetWebsiteFetcher()
                        ?? this.factory.GetLocalDatabaseFetcher();
        return priceFetcher.GetPrice(id);
    }
}

Chaque méthode de la fabrique retourne bien sûr un IProductPriceFetcher, avec la note que les deux premières peuvent échouer et retourner null; j'ai supposé que GetLocalDatabaseFetcher renverrait toujours un objet significatif cependant.

Mes "interrogations générales..."

En cas de succès de l'appel au service web/site web, je souhaite que le prix récupéré soit inséré dans la base de données locale, comme cas de secours futur. Maintenant ma question est: quelle partie du code ci-dessus devrait être responsable de cela? Devrait-il s'agir de l'un des fetchers web concrets qui retourne le prix? Ou le fetcher "agrégateur" (MainFetcher), car il a aussi la connaissance de la source du prix? Devrais-je déclencher un événement? Injecter une autre interface avec des appels à la base de données? Changer le design pour être mieux?

Pourquoi est-ce même devenu un problème pour moi? Eh bien, j'ai essayé de garder le code propre (ne vous inquiétez pas, c'est juste un projet personnel pour mon temps libre - exactement pour résoudre des problèmes comme celui-ci) probablement en gardant à l'esprit le SRP/SoC. Maintenant, il semble que j'ai du mal à passer d'un tel état d'esprit - je veux dire, comment quelque chose qui récupère des pages web pourrait-il également insérer des données dans une base de données? Oh, viens on!

2voto

John Bledsoe Points 7507

Si vous souhaitez avoir une conception super-découpée, je mettrais en œuvre une classe Decorator comme suit et l'utiliserais pour envelopper à la fois WebServiceFetcher et WebsiteFetcher :

class DatabaseCachingFetcherDecorator : IProductPriceFetcher
{
    private readonly IProductPriceFetcher innerFetcher;

    public DatabaseCachingFetcherDecorator(IProductPriceFetcher fetcher)
    {
        this.innerFetcher = fetcher;
    }

    public double GetPrice(int id)
    {
        double price = this.innerFetcher.GetPrice(id);

        if (price != 0) // or some other value representing "price not found"
        {
            SavePriceToDatabase(id, price);
        }

        return price;
    }

    private SavePriceToDatabase(int id, double price)
    {
        // TODO: Implement...
    }
}

Ensuite, votre fabrique implémenterait les méthodes suivantes :

public IProductPriceFetcher GetWebServiceFetcher()
{
    return new DatabaseCachingFetcherDecorator(new WebServiceFetcher());
}

public IProductPriceFetcher GetWebsiteFetcher()
{
    return new DatabaseCachingFetcherDecorator(new WebsiteFetcher());
}

Cette conception découple vos fetchers réels de votre mécanisme de mise en cache.

EDIT : J'ai mal interprété votre conception légèrement avec cette réponse, car j'ai supposé que la méthode GetPrice retournerait une sorte de valeur NULL si le prix ne pouvait pas être récupéré, plutôt que la fabrique retournant une valeur NULL. Je pense que la fabrique retournant NULL n'est pas optimale, car la responsabilité d'une fabrique est de retourner des objets de manière fiable. Je envisagerais de modifier votre interface de méthode GetPrice pour retourner peut-être un double?, pour permettre une valeur "prix non trouvé".

1voto

Paul Michalik Points 3060

Il me semble que vous avez besoin d'un "Cache". La mise en cache est généralement implémentée soit comme une sorte d'aspect ou de dépendance que vous injectez dans votre implémentation de Fetcher. Ci-dessous, je suppose IPriceCache avec une sorte d'interface IDictionary, mais vous pouvez bien sûr insérer n'importe quelle abstraction dont vous avez besoin. Je suggère également d'abstraire les sources de données pour les fetchers de prix...:

class MainFetcher : IPriceFetcher {

 IEnumerable< IPriceSource > mSource;
 IPriceCache mCache;

 public MainFetcher( IEnumerable< IPriceSource > pSource, IPriceCache pCache )
 {
     mSource = pSource;
     mCache = pCache; 
 }

 public double GetPrice(int pID)
 {
     double tPrice;
     // récupérer depuis le cache
     if (mCache.TryGet(pID, out tPrice) {
         return tPrice;
     } else {
         // lance une exception si aucune source n'est trouvée
         tPrice = mSource
             .First(tArg => tArg != null)
             .GetPrice(pID);
         // ajouter au cache
         mCache.Add(pID, tPrice);
     }
 }
}

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