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-ով (կամ օգտվողի այլ կամայական ինդեքսներով) որոնման մեթոդներ՝ առանց բոլոր օբյեկտների վրա ամբողջությամբ կրկնելու:

Օբյեկտներ ավելացնելիս ObjectRepository բաժանորդագրությունն ավելացվում է դրանց հատկությունները փոխելու համար, ուստի հատկությունների ցանկացած փոփոխություն նաև հանգեցնում է նրան, որ այս օբյեկտը ավելացվում է գրելու հերթում: 
Արտաքինից հատկությունների թարմացումը նույնն է, ինչ POCO օբյեկտի հետ աշխատելը.

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

Դուք կարող եք ջնջել օբյեկտը հետևյալ եղանակներով.

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

Սա նաև ավելացնում է օբյեկտը ջնջման հերթում:

Ինչպե՞ս է աշխատում խնայողությունը:

ObjectRepository երբ վերահսկվող օբյեկտները փոխվում են (կամ ավելացնելով կամ ջնջելով, կամ փոխելով հատկությունները), բարձրացնում է իրադարձություն Մոդելը փոխված էբաժանորդագրվել է Պահեստավորում. Իրականացումներ Պահեստավորում երբ իրադարձություն է տեղի ունենում Մոդելը փոխված է փոփոխությունները դրվում են 3 հերթում՝ ավելացնելու, թարմացնելու և ջնջելու համար:

Նաև իրականացումներ Պահեստավորում սկզբնավորումից հետո նրանք ստեղծում են ժամանակաչափ, որը ստիպում է փոփոխությունները պահպանել յուրաքանչյուր 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-ը օբյեկտի մոդելի փոխարեն
  • Գտեք այլ օրինակներ (բոլոր ատյանների վերջնակետերը ավելացնե՞լ կարգավորումներին, udp-ի հայտնաբերում? master/slave?)
  • Կրկնօրինակեք EventLog օրինակների միջև ցանկացած համաձայնության ալգորիթմի միջոցով, ինչպիսին է RAFT-ը:

Ինձ անհանգստացնում է նաև մեկ այլ խնդիր՝ կասկադային ջնջում, կամ այլ օբյեկտներից հղումներ ունեցող օբյեկտների ջնջման դեպքերի հայտնաբերում։ 

Աղբյուրի կոդը

Եթե ​​դուք կարդացել եք մինչև այստեղ, ապա մնում է միայն կարդալ կոդը, այն կարելի է գտնել GitHub-ում.
https://github.com/DiverOfDark/ObjectRepository

Source: www.habr.com

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