ObjectRepository - .NET pola panyimpenan ing memori kanggo proyek omah sampeyan

Apa nyimpen kabeh data ing memori?

Kanggo nyimpen data situs web utawa backend, kepinginan pisanan saka wong sing waras yaiku milih database SQL. 

Nanging kadhangkala dipikirake yen model data ora cocok kanggo SQL: umpamane, nalika mbangun telusuran utawa grafik sosial, sampeyan kudu nggoleki hubungan sing kompleks ing antarane obyek. 

Kahanan sing paling ala yaiku nalika sampeyan kerja ing tim lan kanca kerja ora ngerti carane nggawe pitakon cepet. Pira wektu sing sampeyan gunakake kanggo ngrampungake masalah N+1 lan mbangun indeks tambahan supaya SELECT ing kaca utama bakal rampung ing wektu sing cukup?

Pendekatan populer liyane yaiku NoSQL. Sawetara taun kepungkur ana akeh hype babagan topik iki - kanggo acara apa wae sing trep, padha masang MongoDB lan seneng karo jawaban ing wangun dokumen json. (Ngomong-ngomong, pira kruk sing kudu dilebokake amarga tautan bunder ing dokumen kasebut?).

Aku suggest nyoba liyane, cara alternatif - kok ora nyoba nyimpen kabeh data ing memori aplikasi, periodik nyimpen kanggo panyimpenan acak (file, database remot)? 

Memori wis dadi murah, lan data apa wae sing bisa kanggo proyek cilik lan medium bakal pas karo memori 1 GB. (Contone, proyek omah favoritku yaiku tracker finansial, sing nyimpen statistik saben dina lan riwayat biaya, saldo, lan transaksi sajrone setaun setengah, mung nggunakake memori 45 MB.)

Pros:

  • Akses menyang data dadi luwih gampang - sampeyan ora perlu kuwatir babagan pitakon, loading kesed, fitur ORM, sampeyan bisa nggarap obyek C # biasa;
  • Ora ana masalah sing ana gandhengane karo akses saka benang sing beda;
  • Cepet banget - ora ana panjaluk jaringan, ora ana terjemahan kode menyang basa pitakon, ora perlu (de) serialisasi obyek;
  • Ditampa kanggo nyimpen data ing wangun apa wae - dadi ing XML ing disk, utawa ing SQL Server, utawa ing Azure Table Storage.

Cons:

  • Skala horisontal ilang, lan minangka asil, panyebaran downtime nul ora bisa ditindakake;
  • Yen aplikasi kacilakan, sampeyan bisa uga kelangan data sebagian. (Nanging aplikasi kita ora nate nabrak, ta?)

Carane ora iku bisa?

Algoritme kaya ing ngisor iki:

  • Ing wiwitan, sambungan digawe karo panyimpenan data, lan data dimuat;
  • Model obyek, indeks utami, lan indeks relasional (1: 1, 1: Akeh) dibangun;
  • Langganan digawe kanggo owah-owahan ing properti obyek (INotifyPropertyChanged) lan kanggo nambah utawa mbusak unsur menyang koleksi (INotifyCollectionChanged);
  • Nalika langganan micu, obyek diganti ditambahake menyang antrian kanggo nulis kanggo panyimpenan data;
  • Owah-owahan ing panyimpenan disimpen sacara periodik (ing wektu) ing benang latar mburi;
  • Nalika metu saka aplikasi, owah-owahan uga disimpen ing panyimpenan.

Tuladha kode

Nambahake dependensi sing dibutuhake

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

Kita njlèntrèhaké model data sing bakal disimpen ing panyimpenan

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

Banjur model obyek:

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

Lan pungkasane, kelas repositori dhewe kanggo ngakses 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();
    }
}

Nggawe conto ObjectRepository:

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

Yen proyek bakal nggunakake HangFire

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

Nglebokake obyek anyar:

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

Kanthi telpon iki, obyek ParentModel ditambahake ing cache lokal lan antrian kanggo nulis menyang database. Mulane, operasi iki njupuk O (1), lan obyek iki bisa langsung digarap.

Contone, kanggo nemokake obyek iki ing repositori lan verifikasi manawa obyek bali minangka conto sing padha:

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

Apa sing kedadeyan? Set () bali TabelKamus, kang ngandhut ConcurrentDictionary lan nyedhiyakake fungsi tambahan indeks primer lan sekunder. Iki ngidini sampeyan duwe cara kanggo nggoleki kanthi Id (utawa indeks panganggo liyane sing sewenang-wenang) tanpa ngulang kabeh obyek.

Nalika nambah obyek menyang ObjectRepository langganan ditambahake kanggo ngganti sifat, supaya sembarang owah-owahan ing properti uga nyebabake obyek iki ditambahake menyang antrian nulis. 
Nganyari properti saka njaba katon padha karo nggarap obyek POCO:

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

Sampeyan bisa mbusak obyek kanthi cara ing ngisor iki:

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

Iki uga nambah obyek menyang antrian pambusakan.

Kepiye cara nyimpen?

ObjectRepository nalika obyek sing diawasi ngganti (nambah utawa mbusak, utawa ngganti sifat), mundhakaken acara Model Digantilangganan Panyimpenan. Implementasine Panyimpenan nalika ana acara Model Diganti owah-owahan dilebokake ing 3 antrian - kanggo nambah, kanggo nganyari, lan kanggo mbusak.

Uga implementasine Panyimpenan marang initialization, padha nggawe wektu sing nimbulaké owah-owahan kanggo disimpen saben 5 detik. 

Kajaba iku, ana API kanggo meksa nyimpen telpon: ObjectRepository.Save().

Sadurunge saben nyimpen, operasi sing ora ana gunane dibuwang dhisik saka antrian (contone, duplikat acara - nalika obyek diganti kaping pindho utawa kanthi cepet ditambahake / dibusak obyek), lan mung nyimpen dhewe. 

Ing kabeh kasus, kabeh obyek saiki disimpen, supaya bisa uga obyek disimpen ing urutan beda saka padha diganti, kalebu versi anyar saka obyek saka nalika padha ditambahake menyang antrian.

Ana apa maneh?

  • Kabeh perpustakaan adhedhasar .NET Standard 2.0. Bisa digunakake ing sembarang proyek .NET modern.
  • API kasebut aman. Koleksi internal dileksanakake adhedhasar ConcurrentDictionary, pawang acara duwe kunci utawa ora butuh. 
    Ing bab mung worth ngelingi iku nelpon ObjectRepository.Save();
  • Indeks sewenang-wenang (mbutuhake keunikan):

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

Sapa sing nggunakake?

Secara pribadi, aku miwiti nggunakake pendekatan iki ing kabeh proyek hobi amarga trep lan ora mbutuhake biaya gedhe kanggo nulis lapisan akses data utawa nyebarake infrastruktur abot. Secara pribadi, nyimpen data ing litedb utawa file biasane cukup kanggo aku. 

Nanging ing jaman kepungkur, nalika wiwitan EscapeTeams sing saiki wis mati (Aku panginten kene iku, dhuwit - nanging ora, pengalaman maneh) - digunakake kanggo nyimpen data ing Azure Table Storage.

Rencana kanggo masa depan

Aku pengin ndandani salah sawijining kekurangan utama pendekatan iki - skala horisontal. Kanggo nindakake iki, sampeyan butuh transaksi sing disebarake (sic!), Utawa nggawe keputusan sing kuat yen data sing padha saka macem-macem kedadeyan ora kudu diganti, utawa supaya bisa diganti miturut prinsip "sing pungkasan sing bener."

Saka sudut pandang teknis, aku bisa ndeleng skema ing ngisor iki:

  • Simpen EventLog lan Snapshot tinimbang model obyek
  • Temokake conto liyane (tambahake titik pungkasan kabeh kedadeyan menyang setelan? Penemuan udp? master/slave?)
  • Tiron antarane acara EventLog liwat algoritma konsensus, kayata RAFT.

Ana uga masalah liyane sing saya kuwatir - pambusakan kaskade, utawa deteksi kasus pambusakan obyek sing nduweni pranala saka obyek liyane. 

Sumber kode

Yen sampeyan wis maca nganti tekan kene, sing isih ana yaiku maca kode kasebut; bisa ditemokake ing GitHub:
https://github.com/DiverOfDark/ObjectRepository

Source: www.habr.com

Add a comment