ObjectRepository - Ev layihələriniz üçün .NET yaddaşdaxili depo nümunəsi

Niyə bütün məlumatları yaddaşda saxlamaq lazımdır?

Veb saytı və ya arxa məlumat məlumatlarını saxlamaq üçün ən sağlam insanların ilk istəyi SQL verilənlər bazası seçmək olacaq. 

Ancaq bəzən ağlıma belə bir fikir gəlir ki, verilənlər modeli SQL üçün uyğun deyil: məsələn, axtarış və ya sosial qrafik qurarkən obyektlər arasında mürəkkəb əlaqələri axtarmaq lazımdır. 

Ən pis vəziyyət bir komandada işlədiyiniz zamandır və həmkarınız sürətli sorğuların necə qurulacağını bilmir. Əsas səhifədəki SEÇMƏni kifayət qədər vaxt ərzində tamamlamaq üçün N+1 problemlərini həll etməyə və əlavə indekslər yaratmağa nə qədər vaxt sərf etmisiniz?

Digər populyar yanaşma NoSQL-dir. Bir neçə il əvvəl bu mövzu ətrafında çox şırınga var idi - hər hansı bir əlverişli hadisə üçün MongoDB yerləşdirdilər və json sənədləri şəklində cavablardan məmnun qaldılar. (yeri gəlmişkən, sənədlərdəki dairəvi keçidlərə görə neçə qoltuq dəyənəyi daxil etməli oldunuz?).

Mən başqa, alternativ metodu sınamağı təklif edirəm - niyə bütün məlumatları proqram yaddaşında saxlamağa, vaxtaşırı təsadüfi yaddaşa (fayl, uzaq verilənlər bazası) saxlamağa çalışmayın? 

Yaddaş ucuzlaşıb və əksər kiçik və orta ölçülü layihələr üçün istənilən mümkün məlumat 1 GB yaddaşa sığacaq. (Məsələn, mənim sevimli ev layihəsi maliyyə izləyicisiİl yarım ərzində mənim xərclərim, balanslarım və əməliyyatlarımın gündəlik statistikasını və tarixçəsini saxlayan , cəmi 45 MB yaddaş sərf edir.)

Pros:

  • Məlumatlara giriş asanlaşır - sorğular, tənbəl yükləmə, ORM funksiyaları barədə narahat olmaq lazım deyil, siz adi C# obyektləri ilə işləyirsiniz;
  • Müxtəlif mövzulardan girişlə bağlı heç bir problem yoxdur;
  • Çox sürətli - heç bir şəbəkə sorğusu, kodun sorğu dilinə tərcüməsi, obyektlərin seriyalaşdırılmasına ehtiyac yoxdur;
  • İstənilən formada məlumatları saxlamaq məqbuldur - istər diskdə XML-də, istər SQL Serverdə, istərsə də Azure Cədvəl Storage-də.

Eksiler:

  • Üfüqi miqyas itirilir və nəticədə sıfır dayanma vaxtı yerləşdirmə həyata keçirilə bilməz;
  • Tətbiq qəzaya uğrasa, məlumatları qismən itirə bilərsiniz. (Ancaq tətbiqimiz heç vaxt çökmür, elə deyilmi?)

Necə işləyir?

Alqoritm aşağıdakı kimidir:

  • Başlanğıcda məlumatların saxlanması ilə əlaqə qurulur və məlumatlar yüklənir;
  • Obyekt modeli, ilkin indekslər və əlaqə indeksləri (1:1, 1:Bir çox) qurulur;
  • Abunəlik obyekt xassələrində dəyişikliklər (INotifyPropertyChanged) və kolleksiyaya elementlər əlavə etmək və ya silmək (INotifyCollectionChanged) üçün yaradılır;
  • Abunə işə salındıqda, dəyişdirilmiş obyekt məlumat yaddaşına yazmaq üçün növbəyə əlavə olunur;
  • Yaddaşa edilən dəyişikliklər vaxtaşırı (taymerdə) arxa planda saxlanılır;
  • Tətbiqdən çıxdığınız zaman dəyişikliklər yaddaşda da saxlanılır.

Kod nümunəsi

Lazımi asılılıqların əlavə edilməsi

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

Yaddaşda saxlanacaq məlumat modelini təsvir edirik

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

Sonra obyekt modeli:

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

Və nəhayət, verilənlərə daxil olmaq üçün depo sinfinin özü:

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

ObjectRepository nümunəsi yaradın:

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

Əgər layihə HangFire istifadə edəcək

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

Yeni obyektin daxil edilməsi:

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

Bu çağırışla obyekt Ana Model həm yerli ön yaddaşa, həm də verilənlər bazasına yazmaq üçün növbəyə əlavə olunur. Buna görə də, bu əməliyyat O(1) alır və bu obyektlə dərhal işləmək olar.

Məsələn, bu obyekti depoda tapmaq və qaytarılan obyektin eyni nümunə olduğunu yoxlamaq üçün:

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

Nə baş verir? Set () qayıdır CədvəlLüğət, ehtiva edir ConcurrentLüğət və ilkin və ikincili indekslərin əlavə funksionallığını təmin edir. Bu, bütün obyektlər üzərində tam təkrarlamadan Id (və ya digər ixtiyari istifadəçi indeksləri) üzrə axtarış metodlarına malik olmağa imkan verir.

Obyektləri əlavə edərkən ObjectRepository onların xassələrini dəyişdirmək üçün abunə əlavə edilir, buna görə də xassələrdəki hər hansı dəyişiklik bu obyektin yazma növbəsinə əlavə olunması ilə nəticələnir. 
Xassələri kənardan yeniləmək POCO obyekti ilə işləmək kimi görünür:

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

Bir obyekti aşağıdakı yollarla silə bilərsiniz:

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

Bu da obyekti silmə növbəsinə əlavə edir.

Qənaət necə işləyir?

ObjectRepository monitorinq edilən obyektlər dəyişdikdə (əlavə etmək və ya silmək və ya xassələri dəyişdirmək) hadisə yaradır Model dəyişdirildiabunə oldu IStorage. Tətbiqlər IStorage hadisə baş verəndə Model dəyişdirildi dəyişikliklər 3 növbəyə qoyulur - əlavə etmək, yeniləmək və silmək üçün.

Həmçinin tətbiqlər IStorage işə salındıqdan sonra onlar hər 5 saniyədən bir dəyişikliklərin saxlanmasına səbəb olan taymer yaradırlar. 

Bundan əlavə, qənaət çağırışını məcbur etmək üçün bir API var: ObjectRepository.Save().

Hər saxlamadan əvvəl mənasız əməliyyatlar əvvəlcə növbələrdən silinir (məsələn, dublikat hadisələr - obyekt iki dəfə dəyişdirildikdə və ya obyektlər tez əlavə olunduqda/çıxarıldıqda) və yalnız bundan sonra yaddaşın özü silinir. 

Bütün hallarda, bütün cari obyekt saxlanılır, buna görə də obyektlərin növbəyə əlavə edildiyi vaxtdan daha yeni versiyaları da daxil olmaqla, dəyişdirildiklərindən fərqli qaydada saxlanılması mümkündür.

Başqa nə var?

  • Bütün kitabxanalar .NET Standard 2.0-a əsaslanır. İstənilən müasir .NET layihəsində istifadə edilə bilər.
  • API mövzu təhlükəsizdir. Daxili kolleksiyalar əsasında həyata keçirilir ConcurrentLüğət, hadisə idarəçilərinin ya kilidləri var, ya da onlara ehtiyac yoxdur. 
    Xatırlamağa dəyər yeganə şey zəng etməkdir ObjectRepository.Save();
  • İxtiyari indekslər (unikallıq tələb edir):

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

Kim istifadə edir?

Şəxsən mən bu yanaşmanı bütün hobbi layihələrində istifadə etməyə başladım, çünki bu, rahatdır və məlumat giriş qatının yazılması və ya ağır infrastrukturun yerləşdirilməsi üçün böyük xərc tələb etmir. Şəxsən mənim üçün adətən məlumatların litedb və ya faylda saxlanması kifayətdir. 

Ancaq keçmişdə, indi fəaliyyət göstərməyən başlanğıc EscapeTeams (Düşündüm ki, burada pul var - amma yox, yenidən təcrübə) - Azure Cədvəl Yaddaşında məlumatları saxlamaq üçün istifadə olunur.

Gələcək üçün planlar

Bu yanaşmanın əsas çatışmazlıqlarından birini düzəltmək istərdim - üfüqi miqyas. Bunu etmək üçün ya paylanmış əməliyyatlara ehtiyacınız var (sic!), ya da müxtəlif instansiyalardan eyni məlumatların dəyişməməsi barədə könüllü qərar qəbul etməlisiniz, ya da “sonuncu kim haqlıdır” prinsipinə uyğun olaraq dəyişməsinə icazə verməlisiniz.

Texniki baxımdan mən mümkün qədər aşağıdakı sxemi görürəm:

  • Obyekt modeli əvəzinə EventLog və Snapshot-u saxlayın
  • Digər nümunələri tapın (bütün instansiyaların son nöqtələrini parametrlərə əlavə edin? udp kəşfi? master/slave?)
  • RAFT kimi hər hansı konsensus alqoritmi vasitəsilə EventLog nümunələri arasında təkrarlayın.

Məni narahat edən başqa bir problem də var - kaskad silinməsi və ya digər obyektlərlə əlaqəsi olan obyektlərin silinməsi hallarının aşkarlanması. 

Mənbə kodu

Buraya qədər oxumusunuzsa, kodu oxumaq qalır; onu GitHub-da tapa bilərsiniz:
https://github.com/DiverOfDark/ObjectRepository

Mənbə: www.habr.com

Добавить комментарий