ObjectRepository - .NET in-memory repository pattern para sa imong mga proyekto sa balay

Ngano nga ibutang ang tanan nga datos sa memorya?

Aron sa pagtipig sa website o backend data, ang unang tinguha sa kadaghanan sa mga tawo nga buotan mao ang pagpili sa SQL database. 

Apan usahay ang hunahuna moabut sa hunahuna nga ang modelo sa datos dili angay alang sa SQL: pananglitan, kung magtukod usa ka pagpangita o sosyal nga graph, kinahanglan nimo pangitaon ang komplikado nga mga relasyon tali sa mga butang. 

Ang pinakagrabe nga sitwasyon mao kung nagtrabaho ka sa usa ka team ug ang usa ka kauban wala mahibal-an kung unsaon paghimo og dali nga mga pangutana. Pila ka oras ang imong gigugol sa pagsulbad sa mga problema sa N+1 ug paghimo og dugang nga mga indeks aron ang SELECT sa main page makompleto sa makatarunganon nga gidugayon sa oras?

Ang laing popular nga pamaagi mao ang NoSQL. Pipila ka tuig ang milabay adunay daghang hype sa palibot niini nga hilisgutan - alang sa bisan unsang kombenyente nga okasyon ilang gipakatap ang MongoDB ug nalipay sa mga tubag sa porma sa mga dokumento sa json (by the way, pila ka crutches ang kinahanglan nimong i-insert tungod sa circular links sa mga dokumento?).

Gisugyot ko nga sulayan ang lain, alternatibong pamaagi - nganong dili sulayan ang pagtipig sa tanan nga datos sa memorya sa aplikasyon, matag karon ug unya nga i-save kini sa random storage (file, remote database)? 

Ang memorya nahimong barato, ug bisan unsa nga posible nga datos alang sa kadaghanan sa gagmay ug medium-kadako nga mga proyekto mohaum sa 1 GB nga memorya. (Pananglitan, ang akong paborito nga proyekto sa balay mao ang pinansyal nga tracker, nga nagtipig sa adlaw-adlaw nga estadistika ug kasaysayan sa akong mga galastuhan, balanse, ug mga transaksyon sulod sa usa ug tunga ka tuig, naggamit lang ug 45 MB sa memorya.)

Mga Pro:

  • Ang pag-access sa datos nahimong mas sayon ​​- dili nimo kinahanglan nga mabalaka mahitungod sa mga pangutana, tapolan nga pagkarga, mga feature sa ORM, nagtrabaho ka sa ordinaryo nga C# nga mga butang;
  • Walay mga problema nga nalangkit sa pag-access gikan sa lain-laing mga hilo;
  • Kusog kaayo - walay mga hangyo sa network, walay paghubad sa code ngadto sa usa ka pangutana nga pinulongan, walay panginahanglan alang sa (de)serialization sa mga butang;
  • Gidawat ang pagtipig sa datos sa bisan unsang porma - kini sa XML sa disk, o sa SQL Server, o sa Azure Table Storage.

Kahinumduman:

  • Nawala ang horizontal scaling, ug isip resulta, ang zero downtime deployment dili mahimo;
  • Kung ang aplikasyon nahagsa, mahimo ka nga mawad-an og data. (Apan ang among aplikasyon wala gyud nag-crash, di ba?)

Unsang paagi kini sa trabaho?

Ang algorithm mao ang mosunud:

  • Sa pagsugod, usa ka koneksyon ang gitukod sa pagtipig sa datos, ug ang datos gikarga;
  • Usa ka modelo sa butang, nag-unang mga indeks, ug mga relational nga indeks (1:1, 1:Daghan) gitukod;
  • Ang usa ka suskrisyon gihimo alang sa mga pagbag-o sa mga kabtangan sa butang (INotifyPropertyChanged) ug alang sa pagdugang o pagtangtang sa mga elemento sa koleksyon (INotifyCollectionChanged);
  • Kung ang suskrisyon na-trigger, ang giusab nga butang idugang sa pila para sa pagsulat sa pagtipig sa datos;
  • Ang mga pagbag-o sa pagtipig gitipig matag karon ug unya (sa usa ka timer) sa usa ka hilo sa background;
  • Kung mogawas ka sa aplikasyon, ang mga pagbag-o gitipigan usab sa pagtipig.

Pananglitan sa code

Pagdugang sa gikinahanglan nga mga dependency

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

Gihubit namon ang modelo sa datos nga itago sa pagtipig

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

Unya ang modelo sa butang:

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

Ug sa katapusan, ang klase sa repository mismo alang sa pag-access sa datos:

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

Paghimo usa ka pananglitan sa ObjectRepository:

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

Kung ang proyekto mogamit sa HangFire

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

Pagsal-ot og bag-ong butang:

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

Uban niini nga tawag, ang butang ParentModel idugang sa lokal nga cache ug sa pila para sa pagsulat sa database. Busa, kini nga operasyon nagkinahanglan og O(1), ug kini nga butang mahimong magamit dayon.

Pananglitan, aron makit-an kini nga butang sa repository ug pamatud-an nga ang gibalik nga butang parehas nga pananglitan:

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

Unsay nahitabo? Gitakda () ningbalik TableDictionary, nga naglangkob sa ConcurrentDictionary ug naghatag dugang nga gamit sa panguna ug sekondaryang mga indeks. Gitugotan ka niini nga adunay mga pamaagi sa pagpangita pinaagi sa Id (o uban pang mga arbitraryong indeks sa tiggamit) nga wala’y hingpit nga pag-uli sa tanan nga mga butang.

Kung magdugang mga butang sa ObjectRepository Ang usa ka suskrisyon gidugang aron mabag-o ang ilang mga kabtangan, busa ang bisan unsang pagbag-o sa mga kabtangan moresulta usab niini nga butang nga idugang sa pila sa pagsulat. 
Ang pag-update sa mga kabtangan gikan sa gawas sama ra sa pagtrabaho sa usa ka butang nga POCO:

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

Mahimo nimong papason ang usa ka butang sa mosunod nga mga paagi:

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

Gidugang usab niini ang butang sa pila sa pagtangtang.

Sa unsang paagi molihok ang pagtipig?

ObjectRepository kung ang gibantayan nga mga butang mausab (bisan pagdugang o pagtangtang, o pagbag-o sa mga kabtangan), nagpataas sa usa ka panghitabo Gibag-o ang Modelonag-subscribe sa Istorage. Mga pagpatuman Istorage kon mahitabo ang usa ka panghitabo Gibag-o ang Modelo Ang mga pagbag-o gibutang sa 3 nga mga pila - alang sa pagdugang, alang sa pag-update, ug alang sa pagtangtang.

Mga implementasyon usab Istorage sa pagsugod, naghimo sila usa ka timer nga hinungdan sa mga pagbag-o nga maluwas matag 5 segundos. 

Dugang pa, adunay usa ka API aron mapugos ang usa ka pagluwas nga tawag: ObjectRepository.Save().

Sa wala pa ang matag pag-save, ang wala’y kahulogan nga mga operasyon tangtangon una sa mga pila (pananglitan, doble nga mga panghitabo - kung ang usa ka butang giusab kaduha o dali nga gidugang/gitangtang ang mga butang), ug pagkahuman ang pagluwas mismo. 

Sa tanan nga mga kaso, ang tibuok nga kasamtangan nga butang gitipigan, mao nga posible nga ang mga butang gitipigan sa lahi nga han-ay kay sa giusab, lakip ang mas bag-ong mga bersyon sa mga butang kay sa panahon nga kini gidugang sa pila.

Unsa pay naa?

  • Ang tanang librarya gibase sa .NET Standard 2.0. Mahimong gamiton sa bisan unsang modernong .NET nga proyekto.
  • Ang API luwas sa thread. Ang mga internal nga koleksyon gipatuman base sa ConcurrentDictionary, ang mga tigdumala sa panghitabo adunay mga kandado o wala magkinahanglan niini. 
    Ang angay lang hinumdoman mao ang pagtawag ObjectRepository.Save();
  • Arbitraryong mga indeks (nagkinahanglan ug pagkatalagsaon):

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

Kinsa ang naggamit niini?

Sa personal, gisugdan nako ang paggamit niini nga pamaagi sa tanan nga mga proyekto sa kalingawan tungod kay kini kombenyente ug wala magkinahanglan daghang mga gasto alang sa pagsulat sa usa ka layer sa pag-access sa data o pag-deploy sa bug-at nga imprastraktura. Sa personal, ang pagtipig sa datos sa litedb o usa ka file kasagaran igo na alang kanako. 

Apan kaniadto, sa dihang ang wala na karon nga pagsugod nga EscapeTeams (Naghunahuna ko nga ania na, kuwarta - apan dili, kasinatian pag-usab) - gigamit sa pagtipig sa datos sa Azure Table Storage.

Mga plano alang sa umaabot

Gusto nakong ayohon ang usa sa mga nag-unang disbentaha niini nga pamaagi - horizontal scaling. Aron mahimo kini, kinahanglan nimo ang mga gipang-apod-apod nga mga transaksyon (sic!), O maghimo usa ka kusgan nga desisyon nga ang parehas nga datos gikan sa lainlaing mga higayon kinahanglan dili magbag-o, o tugoti sila nga magbag-o sumala sa prinsipyo nga "kinsa ang katapusan ang husto."

Gikan sa teknikal nga punto sa panglantaw, akong nakita ang mosunod nga laraw kutob sa mahimo:

  • Tipigi ang EventLog ug Snapshot imbes nga modelo sa butang
  • Pangita ug uban pang mga instance (idugang ang mga endpoint sa tanang instance sa mga setting? udp discovery? master/slave?)
  • Pagkopya tali sa mga panghitabo sa EventLog pinaagi sa bisan unsang consensus algorithm, sama sa RAFT.

Adunay lain usab nga problema nga nakapabalaka kanako - ang pagtangtang sa kaskad, o pag-ila sa mga kaso sa pagtangtang sa mga butang nga adunay mga link gikan sa ubang mga butang. 

Source code

Kung nabasa na nimo hangtod dinhi, nan ang nahabilin mao ang pagbasa sa code; kini makita sa GitHub:
https://github.com/DiverOfDark/ObjectRepository

Source: www.habr.com

Idugang sa usa ka comment