ObjectRepository - Намунаи анбори хотираи .NET барои лоиҳаҳои хонаи шумо

Чаро ҳамаи маълумотро дар хотира нигоҳ доред?

Барои нигоҳ доштани вебсайт ё маълумоти пуштибонӣ, аввалин хоҳиши аксари одамони солим интихоби пойгоҳи додаҳои SQL хоҳад буд. 

Аммо баъзан фикре меояд, ки модели додаҳо барои SQL мувофиқ нест: масалан, ҳангоми сохтани графики ҷустуҷӯ ё иҷтимоӣ, шумо бояд муносибатҳои мураккаби байни объектҳоро ҷустуҷӯ кунед. 

Вазъияти бадтарин он аст, ки шумо дар як гурӯҳ кор мекунед ва ҳамкоратон намедонад, ки чӣ тавр сохтани дархостҳои зуд. Шумо барои ҳалли масъалаҳои N+1 ва сохтани индексҳои иловагӣ чӣ қадар вақт сарф кардед, то SELECT дар саҳифаи асосӣ дар вақти мувофиқ анҷом ёбад?

Равиши дигари маъмул NoSQL мебошад. Якчанд сол пеш дар атрофи ин мавзӯъ сару садоҳои зиёд вуҷуд дошт - барои ҳама гуна мавридҳои мувофиқ онҳо MongoDB-ро ҷойгир карданд ва аз ҷавобҳо дар шакли ҳуҷҷатҳои json қаноатманд буданд. (дар омади гап, аз сабаби истинодҳои даврӣ дар ҳуҷҷатҳо шумо бояд чанд асобача гузоред?).

Ман тавсия медиҳам, ки усули дигари алтернативӣ кӯшиш кунед - чаро кӯшиш накунед, ки тамоми маълумотро дар хотираи барнома нигоҳ доред ва онро давра ба давра дар нигаҳдории тасодуфӣ (файл, пойгоҳи додаҳои дурдаст) захира кунед? 

Хотира арзон шуд ва ҳама гуна маълумоти имконпазир барои аксари лоиҳаҳои хурд ва миёна ба 1 ГБ хотира мувофиқат мекунанд. (Масалан, лоиҳаи дӯстдоштаи ман ин аст пайгирии молиявӣ, ки омори ҳаррӯза ва таърихи хароҷот, тавозун ва транзаксияҳои маро дар тӯли якуним сол нигоҳ медорад, ҳамагӣ 45 МБ хотираро истеъмол мекунад.)

Тарафдор:

  • Дастрасӣ ба маълумот осонтар мешавад - ба шумо лозим нест, ки дар бораи дархостҳо, боркунии танбалӣ, хусусиятҳои ORM хавотир шавед, шумо бо объектҳои оддии C# кор мекунед;
  • Мушкилоти марбут ба дастрасӣ аз риштаҳои гуногун вуҷуд надорад;
  • Хеле зуд - ҳеҷ гуна дархостҳои шабакавӣ, тарҷумаи код ба забони пурсиш, зарурати (де)сериализатсияи объектҳо;
  • Нигоҳ доштани маълумот дар ҳама гуна шакл қобили қабул аст - хоҳ он дар XML дар диск, хоҳ дар SQL Server, хоҳ дар Azure Table Storage.

Эзоҳ:

  • Миқёси уфуқӣ гум мешавад ва дар натиҷа, ҷойгиркунии сифри бекористӣ анҷом дода намешавад;
  • Агар барнома суқут кунад, шумо метавонед маълумотро қисман гум кунед. (Аммо барномаи мо ҳеҷ гоҳ суқут намекунад, дуруст?)

Чӣ тавр он кор мекунад?

Алгоритм чунин аст:

  • Дар оғоз, пайвастшавӣ бо нигаҳдории маълумот муқаррар карда мешавад ва маълумот бор карда мешавад;
  • Модели объект, индексҳои ибтидоӣ ва индексҳои релятсионӣ (1:1, 1:Бисёр) сохта шудаанд;
  • Обуна барои тағирот дар хосиятҳои объект (INotifyPropertyChanged) ва барои илова кардан ё хориҷ кардани элементҳо ба коллексия (INotifyCollectionChanged) сохта мешавад;
  • Вақте ки обуна оғоз мешавад, объекти тағирёфта ба навбат барои навиштан ба анбори додаҳо илова карда мешавад;
  • Тағирот дар нигаҳдорӣ давра ба давра (дар таймер) дар риштаи замина захира карда мешаванд;
  • Вақте ки шумо аз барнома хориҷ мешавед, тағиротҳо инчунин дар анбор захира карда мешаванд.

Намунаи код

Илова кардани вобастагии зарурӣ

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

Мо модели маълумотеро тавсиф мекунем, ки дар анбор нигоҳ дошта мешавад

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

Сипас модели объект:

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

Ва ниҳоят, худи синфи репозиторий барои дастрасӣ ба маълумот:

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:

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

Агар лоиҳа HangFire-ро истифода барад

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

Ворид кардани объекти нав:

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

Бо ин даъват объект Модели волидайн ҳам ба кеши маҳаллӣ ва ҳам ба навбат барои навиштан ба пойгоҳи додаҳо илова карда мешавад. Аз ин рӯ, ин амалиёт O(1) -ро мегирад ва бо ин объект фавран кор кардан мумкин аст.

Масалан, барои пайдо кардани ин объект дар анбор ва тафтиш кардани он, ки объекти баргардонидашуда ҳамон мисол аст:

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

Чӣ гап? Танзим () бармегардад Луғати ҷадвал, ки дар бар мегирад Луғати ҳамзамон ва функсияҳои иловагии индексҳои ибтидоӣ ва миёнаро таъмин мекунад. Ин ба шумо имкон медиҳад, ки усулҳои ҷустуҷӯро аз рӯи Id (ё дигар индексҳои истифодабарандаи худсарона) бидуни такрори пурраи ҳама объектҳо дошта бошед.

Ҳангоми илова кардани объектҳо ба Объект репозиторий барои тағир додани хосиятҳои онҳо обуна илова карда мешавад, бинобар ин ҳар гуна тағирот дар хосиятҳо низ боиси он мегардад, ки ин объект ба навбати навиштан илова карда мешавад. 
Навсозии хосиятҳо аз берун ба кор бо объекти POCO монанд аст:

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

Шумо метавонед объектро бо роҳҳои зерин нест кунед:

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

Ин инчунин объектро ба навбати ҳазф илова мекунад.

Сарфакорӣ чӣ гуна кор мекунад?

Объект репозиторий вақте ки объектҳои назоратшаванда тағир меёбанд (илова кардан ё нест кардан ё тағир додани хосиятҳо), ҳодисаро ба вуҷуд меорад Модели иваз карда шудобуна шуд ISзабор. Амалиёт ISзабор вақте ки ҳодиса рӯй медиҳад Модели иваз карда шуд тағйирот ба 3 навбат гузошта мешаванд - барои илова кардан, навсозӣ ва нест кардан.

Инчунин татбиқ ISзабор ҳангоми оғозёбӣ, онҳо таймереро эҷод мекунанд, ки тағиротҳоро дар ҳар 5 сония захира мекунанд. 

Илова бар ин, API барои маҷбур кардани занги захиравӣ мавҷуд аст: ObjectRepository.Save().

Пеш аз ҳар як нигоҳдорӣ, аввал амалҳои бемаънӣ аз навбат хориҷ карда мешаванд (масалан, рӯйдодҳои такрорӣ - вақте ки объект ду маротиба иваз карда шуд ё объектҳои зуд иловашуда/нест карда шуданд) ва танҳо баъд худи захиракунӣ. 

Дар ҳама ҳолатҳо, тамоми объекти ҷорӣ захира карда мешавад, бинобар ин мумкин аст, ки объектҳо бо тартиби дигаре, ки тағир дода шудаанд, аз ҷумла версияҳои навтари объектҳо нисбат ба вақти ба навбат иловашуда захира карда шаванд.

Боз чӣ ҳаст?

  • Ҳама китобхонаҳо ба .NET Standard 2.0 асос ёфтаанд. Дар ҳама гуна лоиҳаи муосири .NET истифода бурдан мумкин аст.
  • API ришта бехатар аст. Коллексияҳои дохилӣ дар асоси амалӣ карда мешаванд Луғати ҳамзамон, коркардкунандагони рӯйдодҳо қуфл доранд ё ба онҳо ниёз надоранд. 
    Ягона чизе, ки бояд дар хотир дошт, занг задан аст ObjectRepository.Save();
  • Индексҳои худсарона (бетакрориро талаб мекунанд):

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

Кӣ онро истифода мебарад?

Шахсан, ман ин равишро дар ҳама лоиҳаҳои маҳфилӣ оғоз кардам, зеро он қулай аст ва барои навиштани қабати дастрасии додаҳо ё густариши инфрасохтори вазнин хароҷоти калонро талаб намекунад. Шахсан, нигоҳ доштани маълумот дар litedb ё файл одатан барои ман кифоя аст. 

Аммо дар гузашта, вақте ки стартапи ҳоло қатъшудаи EscapeTeams (Ман фикр мекардам, ки ин ҷо пул аст - аммо не, боз таҷриба) - барои нигоҳ доштани маълумот дар Azure Table Storage истифода мешавад.

Нақшаҳои оянда

Мехохам яке аз камбудихои асосии ин равиш — микьёси горизонталиро ислох кунам. Барои ин ба шумо ё транзаксияҳои тақсимшуда лозим аст (sic!), ё қарори қавӣ қабул кунед, ки ҳамон як маълумот аз инстанцияҳои гуногун набояд тағир ёбад ё бигзор онҳо мувофиқи принсипи "ки охирин аст, дуруст аст" тағир ёбад.

Аз нуқтаи назари техникӣ, ман нақшаи зеринро то ҳадди имкон мебинам:

  • Ба ҷои модели объекти EventLog ва Snapshot захира кунед
  • Дигар мисолҳоро пайдо кунед (нуқтаҳои охири ҳама мисолҳоро ба танзимот илова кунед? Discovery udp? устод/ғулом?)
  • Тавассути ҳама гуна алгоритми консенсус, ба монанди RAFT, байни мисолҳои EventLog такрор кунед.

Мушкилоти дигаре низ ҳаст, ки маро ба ташвиш меорад - ҳазфи каскадӣ ё ошкор кардани ҳолатҳои ҳазфи объектҳое, ки аз дигар объектҳо пайванд доранд. 

Рамзи манбаъ

Агар шумо то ин ҷо хонда бошед, пас танҳо хондани код боқӣ мемонад; онро дар GitHub пайдо кардан мумкин аст:
https://github.com/DiverOfDark/ObjectRepository

Манбаъ: will.com

Илова Эзоҳ