using System.Collections.Immutable; using Connected.Entities; using Connected.Entities.Storage; using Connected.Notifications.Events; using Connected.ServiceModel; using Connected.Services; namespace Common.Types.TaxRates; internal class TaxRateOps { /// /// Queries all records. /// internal sealed class Query : ServiceFunction?> { /// /// Creates a new instance of the class. /// /// The cache where the records are stored. public Query(ITaxRateCache cache, IStorageProvider storage) { Cache = cache; Storage = storage; } private ITaxRateCache Cache { get; } public IStorageProvider Storage { get; } protected override async Task?> OnInvoke() { var r = await (from dc in Storage.Open() select dc).WithArguments(Arguments).AsEntities(); /* * We simply return all records that exist in the system. */ return await (from dc in Cache select dc).WithArguments(Arguments).AsEntities(); } } /// /// Returns with the specified id or null /// if the record for the specified id does not exist. /// internal sealed class Select : ServiceFunction, ITaxRate?> { public Select(ITaxRateCache cache) { Cache = cache; } private ITaxRateCache Cache { get; } protected override async Task OnInvoke() { /* * Filter cache by the id. */ return await (from dc in Cache where dc.Id == Arguments.Id select dc).AsEntity(); } } /// /// Returns with the specified name and rate or null if /// the record with the specified arguments does not exist. /// internal sealed class SelectByRate : ServiceFunction { public SelectByRate(ITaxRateCache cache) { Cache = cache; } private ITaxRateCache Cache { get; } protected override async Task OnInvoke() { return await (from dc in Cache where string.Equals(dc.Name, Arguments.Name, StringComparison.OrdinalIgnoreCase) && dc.Rate == Arguments.Rate select dc).AsEntity(); } } /// /// Returns List of for the specified set of ids. Use this method when joining data /// to other entities. /// internal sealed class Lookup : ServiceFunction, ImmutableList?> { public Lookup(ITaxRateCache cache) { Cache = cache; } private ITaxRateCache Cache { get; } protected override async Task?> OnInvoke() { /* * Use simple linq join for the specified set of ids. */ return await (from dc in Cache where Arguments.IdList.Contains(dc.Id) select dc).AsEntities(); } } /// /// Inserts a new and returns its Id. /// internal sealed class Insert : ServiceFunction { public Insert(ITaxRateService taxRateService, IStorageProvider storage, IEventService events, ITaxRateCache cache) { TaxRateService = taxRateService; Storage = storage; Events = events; Cache = cache; } private ITaxRateService TaxRateService { get; } private IStorageProvider Storage { get; } private IEventService Events { get; } private ITaxRateCache Cache { get; } protected override async Task OnInvoke() { /* * First, create a new entity from the passed arguments and mark its state as new. This will * signal the DatabaseContext to perform an insert operation when calling the Update. */ var entity = Arguments.AsEntity(State.New); /* * Call update on the DatabaseContext. This call will return a new ITaxRate of the inserted * entity. */ return (await Storage.Open().Update(entity)).Id; } protected override async Task OnCommitted() { /* * At this stage, the transaction has been commited which means * record is definitely permanently stored in the database. * We are making a simple call to the cache to refresh the item with the * new id. Cache will query a database for new record and will store it * in memory. */ await Cache.Refresh(Result); /* * Now trigger the inserted event which will notify all components hooked in the * same process, out of process scale out instances and clients (wasm). */ await Events.Enqueue(this, TaxRateService, ServiceEvents.Inserted, Result); } } /// /// Permanently deletes a from the system. /// /// /// This method gets called after all /// middleware passed successfuly. /// internal sealed class Delete : ServiceAction> { public Delete(ITaxRateService taxRateService, IStorageProvider storage, ITaxRateCache cache, IEventService events) { TaxRateService = taxRateService; Storage = storage; Cache = cache; Events = events; } private ITaxRateService TaxRateService { get; } private IStorageProvider Storage { get; } private ITaxRateCache Cache { get; } private IEventService Events { get; } protected override async Task OnInvoke() { /* * we don't need a reference to a record here because delete uses only * an id. The overhead would only occur in cases if the record wouldn't exist * but this is not the job of the operation. The Validators should theoretically * reject such an operation. */ await Storage.Open().Update(new TaxRate { Id = Arguments.Id, State = State.Deleted }); } protected override async Task OnCommitted() { /* * Once all the transactions have been commited remove the non existing record * from the cache. */ await Cache.Remove(Arguments.Id); /* * And notify audience about the event. */ await Events.Enqueue(this, TaxRateService, ServiceEvents.Deleted, Arguments.Id); } } /// /// Updates the entity. /// internal sealed class Update : ServiceAction { public Update(ITaxRateService taxRateService, IStorageProvider storage, ITaxRateCache cache, IEventService events) { TaxRateService = taxRateService; Storage = storage; Cache = cache; Events = events; } private IStorageProvider Storage { get; } private ITaxRateCache Cache { get; } private IEventService Events { get; } private ITaxRateService TaxRateService { get; } protected override async Task OnInvoke() { /* * TaxRate is concurrency entity which means the platform takes care of * data consistency. Data consistency means we can't overwrite updates made * by others. * Updating Concurrency entity thus requires a bit more logic. We must use a retry logic * in case of Concurrency failure. We'll call Update method with reload lambda function. */ var entity = SetState(await Load()); await Storage.Open().Update(entity, Arguments, async () => { /* * The concurrency exception occured. * Refresh the entry from the cache. This will load a new version from * a database. */ await Cache.Refresh(Arguments.Id); return SetState(await Load()); }); } private async Task Load() => await (from dc in Cache where dc.Id == Arguments.Id select dc).AsEntity(); protected override async Task OnCommitted() { /* * Once the update is complete refresh the cache because the database assigned a new timestamp * to an entity and any sunsequent update would fail anyway. */ await Cache.Refresh(Arguments.Id); /* * Now trigger the distributed event notifying the update has completed. */ await Events.Enqueue(this, TaxRateService, ServiceEvents.Updated, Arguments.Id); } } }