ObjectRepository - .NET dina-memori pola gudang pikeun proyék-proyék imah anjeun

Naha nyimpen sadaya data dina mémori?

Pikeun nyimpen situs wéb atanapi data backend, kahayang pangheulana jalma anu waras nyaéta milih database SQL. 

Tapi kadang pikiran datang ka pikiran yén modél data teu cocog pikeun SQL: contona, nalika ngawangun pilarian atawa grafik sosial, Anjeun kudu neangan hubungan kompléks antara objék. 

Kaayaan anu paling parah nyaéta nalika anjeun damel dina tim sareng batur sapagawean henteu terang kumaha carana ngawangun patarosan gancang. Sabaraha waktos anjeun nyéépkeun masalah N + 1 sareng ngawangun indéks tambahan supados SELECT dina halaman utama bakal réngsé dina waktos anu wajar?

Pendekatan populér séjén nyaéta NoSQL. Sababaraha taun ka pengker aya seueur hype di sekitar topik ieu - pikeun kasempetan naon waé aranjeunna nyebarkeun MongoDB sareng senang sareng jawaban dina bentuk dokumén json (Ku jalan kitu, sabaraha crutches anjeun kudu nyelapkeun alatan tumbu sirkular dina dokumén?).

Kuring ngajukeun nyobian sejen, metoda alternatif - naha henteu coba nyimpen sakabeh data dina mémori aplikasi, périodik simpen ka gudang acak (file, database jauh)? 

Mémori geus jadi mirah, sarta sagala data mungkin keur paling proyék leutik sarta sedeng-ukuran bakal cocog kana 1 memori GB. (Contona, proyék bumi karesep kuring nyaéta tracker finansial, nu nyimpen statistik poean jeung sajarah expenses, kasaimbangan, jeung transaksi kuring salila sataun satengah, meakeun ukur 45 MB memori.)

pro:

  • Aksés ka data janten langkung gampang - anjeun teu kedah hariwang ngeunaan patarosan, loading puguh, fitur ORM, anjeun damel sareng objék C # biasa;
  • Aya henteu masalah pakait sareng aksés ti threads béda;
  • Gancang pisan - teu aya pamundut jaringan, teu aya tarjamahan kode kana basa pamundut, teu kedah (de) serialisasi objék;
  • Ditarima pikeun nyimpen data dina bentuk naon waé - boh dina XML dina disk, atanapi dina SQL Server, atanapi dina Azure Table Storage.

kontra:

  • Skala horisontal leungit, sarta salaku hasilna, enol downtime deployment teu bisa dipigawé;
  • Upami aplikasi ngadat, anjeun tiasa kaleungitan sawaréh data. (Tapi aplikasi kami henteu pernah ngadat, henteu?)

Kumaha carana sangkan eta pagawean?

Algoritme na sapertos kieu:

  • Dina mimiti, sambungan dijieun jeung neundeun data, sarta data dimuat;
  • Model objék, indéks primér, jeung indéks relational (1:1, 1: Loba) diwangun;
  • Langganan dijieun pikeun parobahan dina sipat obyék (INotifyPropertyChanged) jeung pikeun nambahkeun atawa miceun elemen kana kumpulan (INotifyCollectionChanged);
  • Nalika langganan dipicu, objék robah ditambahkeun kana antrian pikeun nulis ka gudang data;
  • Parobahan gudang disimpen périodik (dina timer a) dina thread tukang;
  • Nalika anjeun kaluar tina aplikasi, parobahan ogé disimpen ka panyimpenan.

Conto Kode

Nambahkeun kagumantungan anu diperyogikeun

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

Urang ngajelaskeun model data anu bakal disimpen di gudang

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

Lajeng model objék:

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

Sarta pamustunganana, kelas gudang sorangan pikeun 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();
    }
}

Jieun conto ObjectRepository:

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

Upami proyek bakal nganggo HangFire

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

Nyelapkeun objék anyar:

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

Kalawan panggero ieu, obyék ParentModel ditambahkeun kana duanana cache lokal jeung antrian pikeun nulis ka database. Ku alatan éta, operasi ieu nyokot O (1), sarta objék ieu bisa digarap langsung.

Contona, pikeun manggihan obyék ieu dina Repository tur pariksa yen obyék balik téh conto sarua:

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

Aya naon? Nyetél () mulih MéjaKamus, nu ngandung ConcurrentDictionary sareng nyayogikeun fungsionalitas tambahan tina indéks primér sareng sekundér. Hal ieu ngamungkinkeun anjeun gaduh padika pikeun milarian ku Id (atanapi indéks pangguna sawenang-wenang sanés) tanpa ngémutan sadayana obyék.

Nalika nambahkeun objék ka ObjectRepository langganan ditambahkeun pikeun ngarobah sipat maranéhanana, jadi sagala parobahan dina sipat ogé ngakibatkeun objék ieu ditambahkeun kana antrian nulis. 
Ngamutahirkeun sipat ti luar katingalina sami sareng damel sareng objék POCO:

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

Anjeun tiasa mupus hiji obyék ku cara kieu:

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

Ieu ogé nambihan obyék kana antrian ngahapus.

Kumaha cara nyimpen?

ObjectRepository lamun objék diawaskeun robah (boh nambahkeun atawa mupus, atawa ngarobah sipat), raises hiji acara Modél Robahngalanggan Panyimpenan. Palaksanaan Panyimpenan nalika hiji kajadian lumangsung Modél Robah parobahan disimpen kana 3 antrian - pikeun nambahkeun, pikeun ngamutahirkeun, sarta pikeun mupus.

Ogé palaksanaan Panyimpenan kana initialization, maranéhna nyieun hiji timer nu ngabalukarkeun parobahan disimpen unggal 5 detik. 

Salaku tambahan, aya API pikeun maksa telepon simpen: ObjectRepository.Save().

Sateuacan unggal ngahemat, operasi anu teu aya artina mimiti dipiceun tina antrian (contona, duplikat kajadian - nalika hiji obyék dirobih dua kali atanapi gancang nambihan/dihapus objék), sareng ngan ukur nyimpen éta sorangan. 

Dina sagala hal, sakabéh obyék ayeuna disimpen, jadi mungkin wae objék disimpen dina urutan béda ti maranéhna dirobah, kaasup versi anyar objék ti dina waktu aranjeunna ditambahkeun kana antrian.

Aya naon deui?

  • Sadaya perpustakaan dumasar kana .NET Standard 2.0. Bisa dipaké dina sagala proyék .NET modern.
  • API nyaeta thread aman. Kumpulan internal dilaksanakeun dumasar kana ConcurrentDictionary, Pawang acara gaduh konci atanapi henteu peryogina. 
    Hiji-hijina hal patut remembering nyaéta panggero ObjectRepository.Save ();
  • Indéks sawenang-wenang (merlukeun uniqueness):

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

Saha anu ngagunakeunana?

Pribadi, kuring mimiti ngagunakeun pendekatan ieu dina sakabéh proyék hobi sabab merenah tur teu merlukeun expenses badag pikeun nulis lapisan aksés data atawa deploying infrastruktur beurat. Pribadi, nyimpen data dina litedb atanapi file biasana cukup pikeun kuring. 

Tapi baheula, nalika ngamimitian EscapeTeams ayeuna-defunct (Teu sangka didieu éta, duit - tapi euweuh, pangalaman deui) - dipaké pikeun nyimpen data dina Azure Table Panyimpenan.

Rencana pikeun mangsa nu bakal datang

Abdi hoyong ngalereskeun salah sahiji kalemahan utama pendekatan ieu - horizontal scaling. Jang ngalampahkeun ieu, anjeun peryogi boh transaksi disebarkeun (sic!), Atawa nyieun kaputusan kuat-willed yén data anu sarua ti instansi béda teu kudu robah, atawa ngantep éta robah nurutkeun prinsip "saha panungtungan bener".

Tina sudut pandang téknis, kuring ningali skéma ieu sabisa:

  • Simpen EventLog sareng Snapshot tinimbang modél obyék
  • Milarian instansi anu sanés (tambahkeun titik tungtung sadaya instansi kana setélan? udp discovery? master/slave?)
  • Réplikasi antara instansi EventLog via algoritma konsensus, sapertos RAFT.

Aya ogé masalah sejen nu worries kuring - cascade ngahapus, atawa deteksi kasus ngahapus objék nu boga tumbu ti objék séjén. 

Kode sumber

Upami anjeun parantos maca dugi ka dieu, maka anu tetep nyaéta maca kodeu; éta tiasa dipendakan dina GitHub:
https://github.com/DiverOfDark/ObjectRepository

sumber: www.habr.com

Tambahkeun komentar