ObjectRepository - mudell ta' repożitorju fil-memorja .NET għall-proġetti tad-dar tiegħek

Għaliex taħżen id-dejta kollha fil-memorja?

Biex taħżen dejta tal-websajt jew backend, l-ewwel xewqa tal-biċċa l-kbira tan-nies sani se tkun li jagħżlu database SQL. 

Iżda xi drabi l-ħsieb jiġi f'moħħu li l-mudell tad-dejta mhuwiex adattat għall-SQL: pereżempju, meta tibni tfittxija jew graff soċjali, għandek bżonn tfittex relazzjonijiet kumplessi bejn l-oġġetti. 

L-agħar sitwazzjoni hija meta taħdem f'tim u kollega ma jkunx jaf kif jibni mistoqsijiet ta' malajr. Kemm qattajt ħin issolvi problemi N+1 u tibni indiċi addizzjonali sabiex il-SELECT fuq il-paġna ewlenija titlesta f'ammont ta' żmien raġonevoli?

Approċċ popolari ieħor huwa NoSQL. Bosta snin ilu kien hemm ħafna hype madwar dan is-suġġett - għal kull okkażjoni konvenjenti huma skjerati MongoDB u kienu kuntenti bit-tweġibiet fil-forma ta 'dokumenti json (mill-mod, kemm kellek ddaħħal krozzi minħabba l-links ċirkolari fid-dokumenti?).

Nissuġġerixxi li tipprova metodu alternattiv ieħor - għaliex ma tippruvax taħżen id-dejta kollha fil-memorja tal-applikazzjoni, perjodikament issalvaha f'ħażna każwali (fajl, database remota)? 

Il-memorja saret irħisa, u kwalunkwe data possibbli għall-biċċa l-kbira tal-proġetti żgħar u ta’ daqs medju se tidħol f’1 GB ta’ memorja. (Pereżempju, il-proġett tad-dar favorit tiegħi huwa tracker finanzjarju, li żżomm l-istatistika ta' kuljum u l-istorja tal-ispejjeż, il-bilanċi u t-tranżazzjonijiet tiegħi għal sena u nofs, tikkonsma biss 45 MB ta' memorja.)

Pros:

  • L-aċċess għad-dejta jsir aktar faċli - m'għandekx għalfejn tinkwieta dwar mistoqsijiet, tagħbija għażżien, karatteristiċi ORM, taħdem ma 'oġġetti C# ordinarji;
  • M'hemm l-ebda problemi assoċjati ma 'aċċess minn ħjut differenti;
  • Mgħaġġel ħafna - l-ebda talbiet tan-netwerk, l-ebda traduzzjoni ta 'kodiċi f'lingwa ta' mistoqsija, l-ebda ħtieġa għal (de)serialization ta 'oġġetti;
  • Huwa aċċettabbli li tinħażen id-dejta fi kwalunkwe forma - kemm jekk f'XML fuq disk, jew f'SQL Server, jew f'Azure Table Storage.

Cons:

  • L-iskala orizzontali jintilef, u b'riżultat ta 'dan, l-iskjerament żero ta' waqfien ma jistax isir;
  • Jekk l-applikazzjoni tiġġarraf, tista' titlef id-dejta parzjalment. (Imma l-applikazzjoni tagħna qatt ma tiġġarraf, hux?)

Kif taħdem?

L-algoritmu huwa kif ġej:

  • Fil-bidu, tiġi stabbilita konnessjoni mal-ħażna tad-dejta, u titgħabba d-dejta;
  • Mudell ta' oġġetti, indiċijiet primarji, u indiċi relazzjonali (1:1, 1:Ħafna) huma mibnija;
  • Jinħoloq abbonament għal bidliet fil-proprjetajiet tal-oġġett (INotifyPropertyChanged) u biex jiżdiedu jew jitneħħew elementi fil-kollezzjoni (INotifyCollectionChanged);
  • Meta l-abbonament jiġi attivat, l-oġġett mibdul jiġi miżjud mal-kju għall-kitba fil-ħażna tad-dejta;
  • Bidliet fil-ħażna jiġu ffrankati perjodikament (fuq tajmer) f'ħajt ta 'sfond;
  • Meta toħroġ mill-applikazzjoni, il-bidliet jiġu ssejvjati wkoll fil-ħażna.

Eżempju tal-Kodiċi

Żieda tad-dipendenzi meħtieġa

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

Aħna niddeskrivu l-mudell tad-dejta li se jinħażen fil-ħażna

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

Imbagħad il-mudell tal-oġġett:

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

U fl-aħħarnett, il-klassi tar-repożitorju nnifisha għall-aċċess għad-dejta:

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

Oħloq istanza ta' ObjectRepository:

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

Jekk il-proġett se juża HangFire

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

Daħħal oġġett ġdid:

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

Ma 'din is-sejħa, l-oġġett ParentModel huwa miżjud kemm mal-cache lokali kif ukoll fil-kju għall-kitba fid-database. Għalhekk, din l-operazzjoni tieħu O(1), u dan l-oġġett jista 'jinħadem immedjatament.

Pereżempju, biex issib dan l-oġġett fir-repożitorju u tivverifika li l-oġġett mibgħut lura huwa l-istess eżempju:

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

X'inhu jiġri? Issettja () prospetti TableDizzjunarju, li fih Dizzjunarju Konkorrenti u jipprovdi funzjonalità addizzjonali ta 'indiċi primarji u sekondarji. Dan jippermettilek li jkollok metodi għat-tiftix bl-Id (jew indiċijiet arbitrarji oħra tal-utent) mingħajr ma tgħaddi kompletament fuq l-oġġetti kollha.

Meta żżid oġġetti għal Repożitorju tal-Oġġetti abbonament huwa miżjud biex jibdlu l-proprjetajiet tagħhom, għalhekk kwalunkwe bidla fil-proprjetajiet tirriżulta wkoll f'dan l-oġġett jiżdied mal-kju tal-kitba. 
L-aġġornament tal-proprjetajiet minn barra jidher l-istess bħal xogħol b'oġġett POCO:

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

Tista' tħassar oġġett bil-modi li ġejjin:

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

Dan iżid ukoll l-oġġett mal-kju tat-tħassir.

Kif jaħdem l-iffrankar?

Repożitorju tal-Oġġetti meta l-oġġetti mmonitorjati jinbidlu (jew iżidu jew iħassru, jew jibdlu l-proprjetajiet), iqajjem avveniment Mudell Mibdulabbonat għal Iħażna. Implimentazzjonijiet Iħażna meta jseħħ avveniment Mudell Mibdul il-bidliet jitqiegħdu fi 3 kjuwijiet - għaż-żieda, għall-aġġornament, u għat-tħassir.

Wkoll implimentazzjonijiet Iħażna mal-inizjalizzazzjoni, joħolqu tajmer li jikkawża li l-bidliet jiġu ffrankati kull 5 sekondi. 

Barra minn hekk, hemm API biex tisforza sejħa ta' salvataġġ: ObjectRepository.Save().

Qabel kull iffrankar, l-ewwel jitneħħew operazzjonijiet bla sens mill-kjuwijiet (pereżempju, avvenimenti duplikati - meta oġġett inbidel darbtejn jew oġġetti miżjuda/mneħħija malajr), u mbagħad biss is-salvataġġ innifsu. 

Fil-każijiet kollha, l-oġġett kurrenti kollu jiġi ffrankat, għalhekk huwa possibbli li l-oġġetti jiġu ssejvjati f'ordni differenti minn kif ġew mibdula, inklużi verżjonijiet aktar ġodda ta 'oġġetti milli fil-ħin li ġew miżjuda fil-kju.

X'hemm aktar?

  • Il-libreriji kollha huma bbażati fuq .NET Standard 2.0. Jista 'jintuża fi kwalunkwe proġett modern .NET.
  • L-API huwa thread safe. Kollezzjonijiet interni huma implimentati bbażati fuq Dizzjunarju Konkorrenti, L-immaniġġjar tal-avvenimenti jew għandhom serraturi jew m'għandhomx bżonnhom. 
    L-unika ħaġa ta’ min jiftakar hi li ċċempel ObjectRepository.Save();
  • Indiċijiet arbitrarji (jeħtieġu l-uniċità):

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

Min jużaha?

Personalment, bdejt nuża dan l-approċċ fil-proġetti kollha tal-passatemp għax huwa konvenjenti u ma jeħtieġx spejjeż kbar għall-kitba ta 'saff ta' aċċess għad-dejta jew l-iskjerament ta 'infrastruttura tqila. Personalment, il-ħażna tad-dejta f'litedb jew f'fajl normalment tkun biżżejjed għalija. 

Iżda fil-passat, meta l-istartjar EscapeTeams li issa m'għadhiex taħdem (Ħsibt li hawn, flus - imma le, esperjenza mill-ġdid) - użat biex jaħżen id-dejta f'Azure Table Storage.

Pjanijiet għall-futur

Nixtieq nirranġa wieħed mill-iżvantaġġi ewlenin ta 'dan l-approċċ - skalar orizzontali. Biex tagħmel dan, għandek bżonn jew tranżazzjonijiet distribwiti (sic!), jew tieħu deċiżjoni b'rieda qawwija li l-istess data minn każijiet differenti m'għandhiex tinbidel, jew tħallihom jinbidlu skont il-prinċipju "min huwa l-aħħar għandu raġun."

Mil-lat tekniku, nara l-iskema li ġejja possibbli:

  • Aħżen EventLog u Snapshot minflok mudell tal-oġġett
  • Sib każijiet oħra (żid l-endpoints tal-istanzi kollha mas-settings? udp discovery? master/slave?)
  • Irreplika bejn l-istanzi ta' EventLog permezz ta' kwalunkwe algoritmu ta' kunsens, bħal RAFT.

Hemm ukoll problema oħra li tinkwieta lili - tħassir ta 'kaskata, jew skoperta ta' każijiet ta 'tħassir ta' oġġetti li għandhom rabtiet minn oġġetti oħra. 

Kodiċi tas-sors

Jekk qrajt it-triq kollha sa hawn, allura kulma fadal huwa li taqra l-kodiċi; jista 'jinstab fuq GitHub:
https://github.com/DiverOfDark/ObjectRepository

Sors: www.habr.com

Żid kumment