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