ASP.NET põhisõltuvuse süstimise parimad tavad, näpunäited ja nipid

Selles artiklis jagan oma kogemusi ja soovitusi sõltuvuse süstimise kasutamiseks ASP.NET Core'i rakendustes. Nende põhimõtete taga on motivatsioon;

  • Tõhus teenuste ja nende sõltuvuste kujundamine.
  • Mitme keermega probleemide ennetamine.
  • Mälulekke ennetamine.
  • Võimalike vigade ennetamine.

Selles artiklis eeldatakse, et olete juba tuttav sõltuvuse süstimise ja ASP.NET Core'iga põhitasandil. Kui ei, lugege kõigepealt läbi ASP.NET-i südamiku sõltuvuse süstimise dokumentatsioon.

Põhitõed

Konstruktori süst

Konstruktori sissepritse kasutatakse teenuse sõltuvuse deklareerimiseks ja saamiseks teenuse konstruktsioonist. Näide:

avalik klass ProductService
{
    privaatne ainult IProductRepository _productRepository;
    avalik tooteteenus (IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    public void Kustuta (int id)
    {
        _productRepository.Delete (id);
    }
}

ProductService süstib oma konstruktorisse sõltuvusena IProductRepository ja kasutab seda siis kustutamismeetodi sees.

Hea tava:

  • Määrake nõutavad sõltuvused teenuse konstruktoris selgesõnaliselt. Seega ei saa teenust luua ilma selle sõltuvusteta.
  • Määrake sisestatud sõltuvus kirjutuskaitstud väljale / atribuudile (selleks, et meetodil ei saaks sellele juhuslikult teist väärtust määrata).

Vara süstimine

ASP.NET Core'i tavaline sõltuvuse süstimise konteiner ei toeta vara süstimist. Kuid võite kasutada ka mõnda muud vara süstimist toetavat konteinerit. Näide:

Microsoft.Extensions.Logging kasutamine;
Microsoft.Extensions.Logging.Abstractions kasutamine;
nimeruum MyApp
{
    avalik klass ProductService
    {
        avalik ILogger  logija {get; komplekt; }
        privaatne ainult IProductRepository _productRepository;
        avalik tooteteenus (IProductRepository productRepository)
        {
            _productRepository = productRepository;
            Logger = NullLogger  .Instance;
        }
        public void Kustuta (int id)
        {
            _productRepository.Delete (id);
            Logger.LogInformation (
                $ "Kustutas toote id = {id}");
        }
    }
}

ProductService kuulutab Loggeri atribuuti avaliku setteriga. Sõltuvuse süstimiskonteineri abil saab Loggeri seadistada, kui see on saadaval (varem registreeritud DI konteinerisse).

Hea tava:

  • Kasutage vara süstimist ainult valikuliste sõltuvuste jaoks. See tähendab, et teie teenus saab korralikult töötada ilma nende sõltuvusteta.
  • Kasutage võimaluse korral objekti Null objekti (nagu selles näites). Vastasel juhul kontrollige sõltuvuse kasutamise ajal alati nullväärtust.

Teenuse leidja

Teenuse lokaatori muster on veel üks viis sõltuvuste saamiseks. Näide:

avalik klass ProductService
{
    privaatne ainult IProductRepository _productRepository;
    privaatselt loetav ILogger  _logger;
    avalik tooteteenus (IServiceProvider serviceProvider)
    {
        _productRepository = serviceProvider
          .GetRequiredService  ();
        _logger = serviceProvider
          .GetService > () ??
            NullLogger  .Instance;
    }
    public void Kustuta (int id)
    {
        _productRepository.Delete (id);
        _logger.LogInformation ($ "Kustutas toote id = {id}");
    }
}

ProductService süstib IServiceProviderit ja lahendab sõltuvusi selle abil. GetRequiredService loobub erandist, kui soovitud sõltuvust pole varem registreeritud. Teisest küljest tagastab GetService sel juhul lihtsalt olematu.

Kui lahendate konstruktoris olevad teenused, vabastatakse need teenuse vabastamisel. Seega ei hooli te konstruktorisiseselt lahendatud teenuste (nagu ehitaja ja kinnisvara süstimine) vabastamisest / võõrandamisest.

Hea tava:

  • Ärge kasutage teenuse lokaatori mustrit, kui vähegi võimalik (kui teenuse tüüp on väljatöötamise ajal teada). Sest see muudab sõltuvused kaudseks. See tähendab, et teenuse eksemplari loomisel pole sõltuvusi hõlpsasti näha. See on eriti oluline ühikatsete jaoks, kus võiksite mõningaid teenuse sõltuvusi mõnitada.
  • Võimalusel lahendage sõltuvused teenindavas konstruktoris. Lahendus teenusmeetodi abil muudab teie rakenduse keerukamaks ja tõrkeohtlikuks. Ma käsitlen probleeme ja lahendusi järgmistes osades.

Teenistuse elu ajad

ASP.NET-i põhisõltuvuse süstimisel on kolm teenistusaega:

  1. Mööduvaid teenuseid luuakse iga kord, kui neid süstitakse või taotletakse.
  2. Ulatuslikud teenused luuakse ulatuse kohta. Veebirakenduses loob iga veebitaotlus uue eraldatud teenuse ulatuse. See tähendab, et ulatuslikud teenused luuakse üldjuhul ühe veebipäringu kohta.
  3. Singletoni teenused luuakse iga DI konteineri kohta. See tähendab üldiselt, et neid luuakse ühe rakenduse jaoks ainult üks kord ja seejärel kasutatakse kogu rakenduse eluea jooksul.

DI konteiner jälgib kõiki lahendatud teenuseid. Teenused vabastatakse ja võõrandatakse nende eluea lõppedes:

  • Kui teenusel on sõltuvusi, vabastatakse see ka automaatselt ja utiliseeritakse.
  • Kui teenus rakendab ID-käsutatavat liidest, kutsutakse teenuse vabastamisel automaatselt käitlemisviis.

Hea tava:

  • Registreeri oma teenused kui vähegi võimalik. Sest mööduvate teenuste kavandamine on lihtne. Üldiselt ei hooli te mitmest niidist ja mälulekkest ning teate, et teenusel on lühike eluiga.
  • Kasutage ulatusliku teenuse kasutusiga hoolikalt, kuna lapseteenuste ulatuse loomisel või nende teenuste kasutamisel muust kui veebirakendusest võib olla keeruline.
  • Kasutage üksikut eluaega hoolikalt, sest siis peate tegelema mitme keermestamise ja võimalike mälulekke probleemidega.
  • Ärge sõltuge üksikute teenuste lühiajalisest või ulatuslikust teenusest. Kuna ajutine teenus muutub üksikserveriks, kui üksikteenus selle süstib, ja see võib tekitada probleeme, kui ajutine teenus pole kavandatud sellist stsenaariumi toetama. ASP.NET Core'i vaikimisi paigaldatud DI-konteineris on sellistel juhtudel juba erandeid.

Teenuste lahendamine meetodikorpuses

Mõnel juhul peate võib-olla oma teenuse meetodil lahendama mõne muu teenuse. Sellistel juhtudel veenduge, et vabastate teenuse pärast kasutamist. Parim viis selle tagamiseks on luua teenuse ulatus. Näide:

avaliku klassi PriceCalculator
{
    privaatne ainult IServiceProvider _serviceProvider;
    avalik hinnakalkulaator (IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    Avalik ujuk arvutage (toote toode, int arv,
      Sisestage taxStrategyServiceType)
    {
        kasutades (var ulatus = _serviceProvider.CreateScope ())
        {
            var taxStrategy = (ITaxStrategy) ulatus.ServiceProvider
              .GetRequiredService (taxStrategyServiceType);
            var hind = toode.Hind * arv;
            tagastushind + taxStrategy.CalculateTax (hind);
        }
    }
}

PriceCalculator süstib IServiceProvideri konstruktorisse ja määrab selle väljale. Seejärel kasutab PriceCalculator seda arvutusmeetodi sees lapseteenuste ulatuse loomiseks. Teenuste lahendamiseks kasutatakse sisendatud _serviceProvideri eksemplari asemel teenust ulatus.ServiceProvider. Seega vabastatakse kõik käsitusulatusest eraldatud teenused automaatselt käskude kasutamise lõpus.

Hea tava:

  • Kui lahendate teenuse meetodi korpuses, looge alati lapseteenuste ulatus, et tagada lahendatud teenuste nõuetekohane vabastamine.
  • Kui meetod saab argumendiks IServiceProvideri, saate selle teenuse otse lahendada, ilma et peaksite seda vabastama / käsutama. Teenuse ulatuse loomine / haldamine on teie meetodile helistava koodi vastutus. Selle põhimõtte järgimine muudab teie koodi puhtamaks.
  • Ärge hoidke viidet lahendatud teenusele! Vastasel korral võib see põhjustada mälulekke ja pääsete teenuse juurde, kui kasutate objekti viidet hiljem (kui lahendatud teenus pole üksik).

Singletoni teenused

Singletoni teenused on üldiselt mõeldud rakenduse oleku säilitamiseks. Vahemälu on hea näide rakenduse olekute kohta. Näide:

avaliku klassi FileService
{
    privaatselt lugetav ConcurrentD Dictionary  _cache;
    avalik FileService ()
    {
        _cache = uus konkreetne sõnaraamat  ();
    }
    avalik bait [] GetFileContent (string filePath)
    {
        tagasta _cache.GetOrAdd (filePath, _ =>
        {
            tagastage File.ReadAllBytes (filePath);
        });
    }
}

FileService vahemällu salvestab lihtsalt faili sisu, et ketta lugemist vähendada. See teenus tuleks registreerida singletonina. Vastasel juhul ei toimi vahemälu ootuspäraselt.

Hea tava:

  • Kui teenusel on olek, peaks see sellele olekule juurdepääsu niiditurvalisel viisil. Kuna kõik taotlused kasutavad samaaegselt sama teenuse eksemplari. Niidi turvalisuse tagamiseks kasutasin sõnaraamatu asemel ConcurrentDictionary.
  • Ärge kasutage üksikute teenuste ulatuslikke või ajutisi teenuseid. Sest ajutised teenused ei pruugi olla niiditurvalised. Kui peate neid kasutama, siis hoolitsege nende teenuste kasutamise eest mitme keermestamise eest (kasutage näiteks lukku).
  • Mälulekked on tavaliselt tingitud üksikute teenuste osutamisest. Neid ei vabastata ega kõrvaldata enne rakenduse lõppu. Niisiis, kui nad klassid kiirendavad (või süstivad), kuid neid ei vabasta / utiliseeri, jäävad nad mällu ka rakenduse lõpuni. Veenduge, et vabastaksite / utiliseeriksite õigel ajal. Vaadake ülaltoodud jaotist Meetodiharu lahendamise teenused.
  • Andmete vahemällu salvestamine (faili sisu selles näites), peaksite looma mehhanismi vahemälu andmete värskendamiseks / kehtetuks muutmiseks, kui algne andmeallikas muutub (kui selle näite korral vahemälu fail muutub kettale).

Reguleerimisala teenused

Ulatuslik eluiga näib kõigepealt olevat hea kandidaat veebipäringu andmete salvestamiseks. Kuna ASP.NET Core loob teenuse ulatuse veebipäringu kohta. Seega, kui registreerite teenuse ulatusena, saab seda veebipäringu ajal jagada. Näide:

avalik klass RequestItemsService
{
    privaatsõnaline sõnaraamat  _items;
    avalik RequestItemsService ()
    {
        _items = uus sõnaraamat  ();
    }
    public void Set (stringi nimi, objekti väärtus)
    {
        _items [nimi] = väärtus;
    }
    avalik objekt Hangi (stringi nimi)
    {
        return _items [nimi];
    }
}

Kui registreerite RequestItemsService'i ulatusena ja sisestate selle kahte erinevasse teenusesse, saate hankida üksuse, mis lisatakse teisest teenusest, kuna nad jagavad sama RequestItemsService eksemplari. Seda me ootame laiaulatuslikest teenustest.

Kuid .. fakt ei pruugi alati selline olla. Kui loote lapseteenuste ulatuse ja lahendate nende rakenduse RequestItemsService, siis saate uue RequestItemsService'i eksemplari ja see ei tööta nii, nagu loodate. Seega ei tähenda ulatuslik teenus alati eksemplari veebipäringu kohta.

Võite arvata, et te ei tee nii ilmset viga (lahendades lapsele mõeldud ulatuse). Kuid see pole viga (väga regulaarne kasutamine) ja juhtum ei pruugi olla nii lihtne. Kui teie teenuste vahel on suur sõltuvusgraafik, ei saa te teada, kas keegi lõi lapsele ulatuse ja otsustas teenuse, mis pakub teist teenust ... mis lõpuks lisab ulatuslikku teenust.

Hea harjutus:

  • Ulatuslikku teenust võib käsitada optimeerimisena, kui seda sisestab veebipäringus liiga palju teenuseid. Seega kasutavad kõik need teenused sama veebitaotluse ajal teenuse ühte eksemplari.
  • Ulatuslikud teenused ei pea olema niidikindlad. Sest neid peaks tavaliselt kasutama üks veebi taotlus / teema. Kuid… sellisel juhul ei tohiks te teenuse ulatusi eri lõimede vahel jagada!
  • Olge ettevaatlik, kui kavandate ulatusliku teenuse, et jagada veebiteenuse kaudu teiste teenuste vahel andmeid (selgitatud eespool). Saate salvestada veebipõhiste päringute andmeid HttpContext-is (sellele juurdepääsu saamiseks sisestage IHttpContextAccessor), mis on turvalisem viis seda teha. HttpContexi eluiga ei ole ulatus. Tegelikult pole seda üldse DI-s registreeritud (sellepärast ei süsti, vaid süstib hoopis IHttpContextAccessor). HttpContextAccessori juurutamine kasutab AsyncLocalit sama HttpContexi jagamiseks veebipäringu ajal.

Järeldus

Sõltuvuse süstimist näib alguses lihtne kasutada, kuid kui te ei järgi mõnda ranget põhimõtet, võib esineda probleeme mitme keermestamise ja mälulekkega. Jagasin omaenda kogemustele tuginedes häid põhimõtteid ASP.NET Boilerplate raamistiku väljatöötamisel.