ObjectRepository - .NET geymslumynstur í minni fyrir heimaverkefnin þín

Af hverju að geyma öll gögnin í minni?

Til að geyma vefsíðu- eða bakendagögn mun fyrsta ósk flestra heilvita fólks vera að velja SQL gagnagrunn. 

En stundum kemur sú hugsun upp í hugann að gagnalíkanið henti ekki fyrir SQL: til dæmis, þegar þú byggir leitar- eða félagslegt línurit, þarftu að leita að flóknum tengslum milli hluta. 

Versta ástandið er þegar þú vinnur í teymi og samstarfsmaður veit ekki hvernig á að búa til skjótar fyrirspurnir. Hversu miklum tíma eyddir þú í að leysa N+1 vandamál og byggja upp viðbótarvísitölur þannig að SELECT á aðalsíðunni myndi klárast á hæfilegum tíma?

Önnur vinsæl nálgun er NoSQL. Fyrir nokkrum árum síðan var mikið efla í kringum þetta efni - fyrir hvaða hentug tækifæri sem er settu þeir upp MongoDB og voru ánægðir með svörin í formi json skjala (við the vegur, hversu margar hækjur þurftirðu að setja inn vegna hringlaga tengla í skjölunum?).

Ég legg til að þú prófir aðra, aðra aðferð - hvers vegna ekki að prófa að geyma öll gögnin í minni forrita, vista þau reglulega í handahófskenndri geymslu (skrá, ytri gagnagrunnur)? 

Minni er orðið ódýrt og öll möguleg gögn fyrir flest lítil og meðalstór verkefni passa í 1 GB af minni. (Til dæmis er uppáhalds heimaverkefnið mitt fjármálaeftirlit, sem geymir daglega tölfræði og sögu um útgjöld mín, stöður og færslur í eitt og hálft ár, eyðir aðeins 45 MB af minni.)

Kostir:

  • Aðgangur að gögnum verður auðveldari - þú þarft ekki að hafa áhyggjur af fyrirspurnum, lata hleðslu, ORM eiginleika, þú vinnur með venjulega C# hluti;
  • Það eru engin vandamál tengd aðgangi frá mismunandi þráðum;
  • Mjög hratt - engar netbeiðnir, engin þýðing á kóða yfir á fyrirspurnarmál, engin þörf á (af)raðgreiningu á hlutum;
  • Það er ásættanlegt að geyma gögn í hvaða formi sem er - hvort sem það er í XML á diski, eða í SQL Server eða í Azure Table Storage.

Gallar:

  • Lárétt stærðarstærð tapast og þar af leiðandi er ekki hægt að útfæra stöðvunartíma á núlli;
  • Ef forritið hrynur gætirðu glatað gögnum að hluta. (En forritið okkar hrynur aldrei, ekki satt?)

Hvernig virkar það?

Reikniritið er sem hér segir:

  • Í upphafi er tenging komið á við gagnageymsluna og gögn eru hlaðin;
  • Hlutalíkan, aðalvísitölur og venslavísitölur (1:1, 1:Margir) eru byggðir;
  • Áskrift er búin til fyrir breytingar á eiginleikum hlutar (INotifyPropertyChanged) og til að bæta við eða fjarlægja þætti í safnið (INotifyCollectionChanged);
  • Þegar áskriftin er ræst er breyttum hlut bætt við biðröðina til að skrifa í gagnageymsluna;
  • Breytingar á geymslunni eru vistaðar reglulega (á tímamæli) í bakgrunnsþræði;
  • Þegar þú hættir forritinu eru breytingar einnig vistaðar í geymslunni.

Dæmi um kóða

Að bæta við nauðsynlegum ósjálfstæði

// Основная библиотека
Install-Package OutCode.EscapeTeams.ObjectRepository
    
// Хранилище данных, в котором будут сохраняться изменения
// Используйте то, которым будете пользоваться.
Install-Package OutCode.EscapeTeams.ObjectRepository.File
Install-Package OutCode.EscapeTeams.ObjectRepository.LiteDb
Install-Package OutCode.EscapeTeams.ObjectRepository.AzureTableStorage
    
// Опционально - если нужно хранить модель данных для Hangfire
// Install-Package OutCode.EscapeTeams.ObjectRepository.Hangfire

Við lýsum gagnalíkaninu sem verður geymt í geymslunni

public class ParentEntity : BaseEntity
{
    public ParentEntity(Guid id) => Id = id;
}
    
public class ChildEntity : BaseEntity
{
    public ChildEntity(Guid id) => Id = id;
    public Guid ParentId { get; set; }
    public string Value { get; set; }
}

Síðan er hlutlíkanið:

public class ParentModel : ModelBase
{
    public ParentModel(ParentEntity entity)
    {
        Entity = entity;
    }
    
    public ParentModel()
    {
        Entity = new ParentEntity(Guid.NewGuid());
    }
    
    public Guid? NullableId => null;
    
    // Пример связи 1:Many
    public IEnumerable<ChildModel> Children => Multiple<ChildModel>(x => x.ParentId);
    
    protected override BaseEntity Entity { get; }
}
    
public class ChildModel : ModelBase
{
    private ChildEntity _childEntity;
    
    public ChildModel(ChildEntity entity)
    {
        _childEntity = entity;
    }
    
    public ChildModel() 
    {
        _childEntity = new ChildEntity(Guid.NewGuid());
    }
    
    public Guid ParentId
    {
        get => _childEntity.ParentId;
        set => UpdateProperty(() => _childEntity.ParentId, value);
    }
    
    public string Value
    {
        get => _childEntity.Value;
        set => UpdateProperty(() => _childEntity.Value, value
    }
    
    // Доступ с поиском по индексу
    public ParentModel Parent => Single<ParentModel>(ParentId);
    
    protected override BaseEntity Entity => _childEntity;
}

Og að lokum, geymsluflokkurinn sjálfur til að fá aðgang að gögnum:

public class MyObjectRepository : ObjectRepositoryBase
{
    public MyObjectRepository(IStorage storage) : base(storage, NullLogger.Instance)
    {
        IsReadOnly = true; // Для тестов, позволяет не сохранять изменения в базу
    
        AddType((ParentEntity x) => new ParentModel(x));
        AddType((ChildEntity x) => new ChildModel(x));
    
        // Если используется Hangfire и необходимо хранить модель данных для Hangfire в ObjectRepository
        // this.RegisterHangfireScheme(); 
    
        Initialize();
    }
}

Búðu til ObjectRepository dæmi:

var memory = new MemoryStream();
var db = new LiteDatabase(memory);
var dbStorage = new LiteDbStorage(db);
    
var repository = new MyObjectRepository(dbStorage);
await repository.WaitForInitialize();

Ef verkefnið mun nota HangFire

public void ConfigureServices(IServiceCollection services, ObjectRepository objectRepository)
{
    services.AddHangfire(s => s.UseHangfireStorage(objectRepository));
}

Að setja inn nýjan hlut:

var newParent = new ParentModel()
repository.Add(newParent);

Með þessu símtali, hluturinn Foreldramódel er bætt við bæði staðbundið skyndiminni og biðröðina til að skrifa í gagnagrunninn. Þess vegna tekur þessi aðgerð O(1) og hægt er að vinna með þennan hlut strax.

Til dæmis, til að finna þennan hlut í geymslunni og sannreyna að hluturinn sem skilað er sé sama tilvikið:

var parents = repository.Set<ParentModel>();
var myParent = parents.Find(newParent.Id);
Assert.IsTrue(ReferenceEquals(myParent, newParent));

Hvað er að gerast? Sett () skilar TaflaOrðabók, sem inniheldur Samhliða orðabók og veitir viðbótarvirkni aðal- og aukavísitölu. Þetta gerir þér kleift að hafa aðferðir til að leita eftir auðkenni (eða öðrum handahófskenndum notendavísitölum) án þess að endurtaka algjörlega yfir alla hluti.

Þegar hlutum er bætt við ObjectRepository áskrift er bætt við til að breyta eiginleikum þeirra, þannig að allar breytingar á eiginleikum leiða einnig til þess að þessum hlut er bætt við skrifaröðina. 
Að uppfæra eiginleika að utan lítur eins út og að vinna með POCO hlut:

myParent.Children.First().Property = "Updated value";

Þú getur eytt hlut á eftirfarandi hátt:

repository.Remove(myParent);
repository.RemoveRange(otherParents);
repository.Remove<ParentModel>(x => !x.Children.Any());

Þetta bætir hlutnum einnig við eyðingarröðina.

Hvernig virkar sparnaður?

ObjectRepository þegar vaktir hlutir breytast (annaðhvort bæta við eða eyða, eða breyta eiginleikum), vekur atburð Fyrirmynd breyttgerðist áskrifandi að ISgeymsla. Útfærslur ISgeymsla þegar atburður á sér stað Fyrirmynd breytt breytingar eru settar í 3 biðraðir - til að bæta við, til að uppfæra og eyða.

Einnig útfærslur ISgeymsla við frumstillingu búa þeir til tímamæli sem veldur því að breytingar eru vistaðar á 5 sekúndna fresti. 

Að auki er API til að þvinga fram vistunarkall: ObjectRepository.Save().

Fyrir hverja vistun eru tilgangslausar aðgerðir fyrst fjarlægðar úr biðröðunum (til dæmis tvítekningar - þegar hlut var breytt tvisvar eða fljótleg viðbót/fjarlæging á hlutum), og aðeins síðan vistunin sjálf. 

Í öllum tilfellum er allt núverandi hlutur vistaður, þannig að það er mögulegt að hlutir séu vistaðir í annarri röð en þeim var breytt, þar á meðal nýrri útgáfur af hlutum en á þeim tíma sem þeim var bætt við röðina.

Hvað er annað?

  • Öll bókasöfn eru byggð á .NET Standard 2.0. Hægt að nota í hvaða nútíma .NET verkefni sem er.
  • API er þráð öruggt. Innri söfnun er útfærð á grundvelli Samhliða orðabók, viðburðastjórnendur annað hvort hafa læsingar eða þurfa þá ekki. 
    Það eina sem vert er að muna er að hringja ObjectRepository.Save();
  • Handahófskenndar vísitölur (þurfa sérstöðu):

repository.Set<ChildModel>().AddIndex(x => x.Value);
repository.Set<ChildModel>().Find(x => x.Value, "myValue");

Hver notar það?

Persónulega byrjaði ég að nota þessa nálgun í öllum tómstundaverkefnum vegna þess að hún er þægileg og krefst ekki mikils útgjalda fyrir að skrifa gagnaaðgangslag eða útfæra þunga innviði. Persónulega nægir mér yfirleitt að geyma gögn í litedb eða skrá. 

En í fortíðinni, þegar gangsetning EscapeTeams sem nú er hætt (Ég hélt að hér væri það, peningar - en nei, reynsla aftur) - notað til að geyma gögn í Azure Table Storage.

Áætlanir fyrir framtíðina

Mig langar að laga einn af helstu ókostum þessarar nálgunar - lárétta skala. Til að gera þetta þarftu annað hvort dreifð viðskipti (sic!), Eða taka viljasterka ákvörðun um að sömu gögn frá mismunandi tilvikum ættu ekki að breytast, eða láta þau breytast í samræmi við meginregluna „hver er síðastur hefur rétt fyrir sér.“

Frá tæknilegu sjónarhorni sé ég eftirfarandi kerfi sem mögulegt er:

  • Geymdu EventLog og Snapshot í stað hlutlíkans
  • Finndu önnur tilvik (bæta endapunktum allra tilvika við stillingarnar? udp uppgötvun? meistari/þræll?)
  • Afritaðu á milli EventLog tilvika með hvaða samþykki sem er, eins og RAFT.

Það er líka annað vandamál sem veldur mér áhyggjum - eyðingu í fossi, eða uppgötvun tilvika um eyðingu á hlutum sem hafa tengla frá öðrum hlutum. 

Kóðinn

Ef þú hefur lesið alla leið hingað, þá er allt sem eftir er að lesa kóðann; hann er að finna á GitHub:
https://github.com/DiverOfDark/ObjectRepository

Heimild: www.habr.com

Bæta við athugasemd