ObjectRepository - .NET in-geheue bewaarplek patroon vir jou huis projekte

Hoekom stoor al die data in die geheue?

Om webwerf- of backend-data te stoor, sal die eerste begeerte van die meeste gesonde mense wees om 'n SQL-databasis te kies. 

Maar soms kom die gedagte by my op dat die datamodel nie geskik is vir SQL nie: wanneer jy byvoorbeeld 'n soektog of sosiale grafiek bou, moet jy na komplekse verwantskappe tussen voorwerpe soek. 

Die ergste situasie is wanneer jy in 'n span werk en 'n kollega nie weet hoe om vinnige navrae te bou nie. Hoeveel tyd het jy daaraan bestee om N+1-probleme op te los en bykomende indekse te bou sodat die SELECT op die hoofblad binne 'n redelike tyd sou voltooi?

Nog 'n gewilde benadering is NoSQL. 'n Paar jaar gelede was daar baie ophef rondom hierdie onderwerp - vir enige gerieflike geleentheid het hulle MongoDB ontplooi en was tevrede met die antwoorde in die vorm van json-dokumente (terloops, hoeveel krukke moes jy insit as gevolg van die omsendbrief skakels in die dokumente?).

Ek stel voor dat u 'n ander, alternatiewe metode probeer - waarom nie probeer om al die data in toepassingsgeheue te stoor en dit periodiek na 'n ewekansige berging (lêer, afgeleë databasis) te stoor nie? 

Geheue het goedkoop geword, en enige moontlike data vir die meeste klein en mediumgrootte projekte sal in 1 GB geheue pas. (My gunsteling tuisprojek is byvoorbeeld finansiële spoorsnyer, wat daaglikse statistieke en geskiedenis van my uitgawes, saldo's en transaksies vir 'n jaar en 'n half hou, verbruik slegs 45 MB geheue.)

Pros:

  • Toegang tot data word makliker - jy hoef nie bekommerd te wees oor navrae, lui laai, ORM-kenmerke nie, jy werk met gewone C#-objekte;
  • Daar is geen probleme wat verband hou met toegang vanaf verskillende drade nie;
  • Baie vinnig - geen netwerkversoeke, geen vertaling van kode in 'n navraagtaal, geen behoefte aan (de)serialisering van voorwerpe nie;
  • Dit is aanvaarbaar om data in enige vorm te stoor - of dit nou in XML op skyf is, of in SQL Server, of in Azure Table Storage.

Nadele:

  • Horisontale skaal gaan verlore, en gevolglik kan geen stilstand ontplooiing gedoen word nie;
  • As die toepassing ineenstort, kan u data gedeeltelik verloor. (Maar ons toepassing val nooit neer nie, reg?)

Hoe werk dit?

Die algoritme is soos volg:

  • Aan die begin word 'n verbinding met die databerging tot stand gebring, en data word gelaai;
  • 'n Objekmodel, primêre indekse en relasionele indekse (1:1, 1:Baie) word gebou;
  • 'n Intekening word geskep vir veranderinge in voorwerpeienskappe (INotifyPropertyChanged) en om elemente by die versameling by te voeg of te verwyder (INotifyCollectionChanged);
  • Wanneer die intekening geaktiveer word, word die veranderde voorwerp by die tou gevoeg om na die databerging te skryf;
  • Veranderinge aan die berging word periodiek (op 'n timer) in 'n agtergronddraad gestoor;
  • Wanneer jy die toepassing verlaat, word veranderinge ook in die berging gestoor.

Kode voorbeeld

Voeg die nodige afhanklikhede by

// Основная библиотека
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

Ons beskryf die datamodel wat in die stoor gestoor sal word

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; }
}

Dan die objekmodel:

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;
}

En laastens, die bewaarplekklas self vir toegang tot data:

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();
    }
}

Skep 'n ObjectRepository-instansie:

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

As die projek HangFire sal gebruik

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

Voeg 'n nuwe voorwerp in:

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

Met hierdie oproep, die voorwerp Ouermodel word by beide die plaaslike kas en die tou gevoeg om na die databasis te skryf. Daarom neem hierdie bewerking O(1), en daar kan dadelik met hierdie voorwerp gewerk word.

Byvoorbeeld, om hierdie voorwerp in die bewaarplek te vind en te verifieer dat die teruggekeerde voorwerp dieselfde geval is:

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

Wat gebeur? Stel () keer terug Tabelwoordeboek, wat bevat Gelyktydige Woordeboek en bied addisionele funksionaliteit van primêre en sekondêre indekse. Dit laat jou toe om metodes te hê om volgens Id (of ander arbitrêre gebruikersindekse) te soek sonder om heeltemal oor alle voorwerpe te herhaal.

Wanneer voorwerpe bygevoeg word ObjectRepository 'n intekening word bygevoeg om hul eienskappe te verander, so enige verandering in eienskappe lei ook daartoe dat hierdie objek by die skryfry gevoeg word. 
Die opdatering van eienskappe van buite lyk dieselfde as om met 'n POCO-voorwerp te werk:

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

Jy kan 'n voorwerp op die volgende maniere uitvee:

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

Dit voeg ook die voorwerp by die uitveewaglys.

Hoe werk besparing?

ObjectRepository wanneer gemonitorde voorwerpe verander (óf byvoeging of uitvee, of eienskappe verander), veroorsaak 'n gebeurtenis Model Veranderingeteken op ISberging. Implementerings ISberging wanneer 'n gebeurtenis plaasvind Model Verander veranderinge word in 3 rye geplaas - vir byvoeging, vir opdatering en om uit te vee.

Ook implementerings ISberging by inisialisering skep hulle 'n timer wat veroorsaak dat veranderinge elke 5 sekondes gestoor word. 

Daarbenewens is daar 'n API om 'n stooroproep af te dwing: ObjectRepository.Save().

Voor elke stoor word betekenislose bewerkings eers uit die rye verwyder (byvoorbeeld, duplikaatgebeurtenisse - wanneer 'n voorwerp twee keer verander is of voorwerpe vinnig bygevoeg/verwyder is), en eers dan die stoor self. 

In alle gevalle word die hele huidige voorwerp gestoor, so dit is moontlik dat voorwerpe in 'n ander volgorde gestoor word as wat hulle verander is, insluitend nuwer weergawes van voorwerpe as toe hulle by die tou gevoeg is.

Wat anders is daar?

  • Alle biblioteke is gebaseer op .NET Standard 2.0. Kan in enige moderne .NET-projek gebruik word.
  • Die API is draad veilig. Interne versamelings word geïmplementeer gebaseer op Gelyktydige Woordeboek, gebeurtenishanteerders het óf slotte óf het dit nie nodig nie. 
    Die enigste ding wat die moeite werd is om te onthou, is om te bel ObjectRepository.Save();
  • Arbitrêre indekse (vereis uniekheid):

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

Wie gebruik dit?

Persoonlik het ek hierdie benadering in alle stokperdjieprojekte begin gebruik omdat dit gerieflik is en nie groot uitgawes verg om 'n datatoegangslaag te skryf of swaar infrastruktuur te ontplooi nie. Persoonlik is die stoor van data in litedb of 'n lêer gewoonlik vir my genoeg. 

Maar in die verlede, toe die nou ontbinde opstart EscapeTeams (Ek het gedink hier is dit, geld – maar nee, ervaar weer) - word gebruik om data in Azure Table Storage te stoor.

Planne vir die toekoms

Ek wil graag een van die belangrikste nadele van hierdie benadering oplos - horisontale skaal. Om dit te doen, benodig jy óf verspreide transaksies (sic!), óf neem 'n sterk wilsbesluit dat dieselfde data van verskillende instansies nie moet verander nie, óf laat hulle verander volgens die beginsel "wie laaste is, is reg."

Uit 'n tegniese oogpunt sien ek die volgende skema as moontlik:

  • Stoor EventLog en Snapshot in plaas van objekmodel
  • Soek ander gevalle (voeg eindpunte van alle gevalle by die instellings? udp-ontdekking? meester/slaaf?)
  • Repliseer tussen EventLog-gevalle via enige konsensusalgoritme, soos RAFT.

Daar is ook 'n ander probleem wat my bekommer - kaskade-skrap, of opsporing van gevalle van verwydering van voorwerpe wat skakels van ander voorwerpe het. 

Bronkode

As jy al die pad tot hier gelees het, dan is al wat oorbly om die kode te lees; dit kan op GitHub gevind word:
https://github.com/DiverOfDark/ObjectRepository

Bron: will.com

Voeg 'n opmerking