You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Logistics/Logistics.Types/Serials/SerialOps.cs

221 lines
6.7 KiB

2 years ago
using System.Collections.Immutable;
using Connected.Caching;
using Connected.Entities;
using Connected.Entities.Storage;
using Connected.Notifications.Events;
using Connected.ServiceModel;
using Connected.Services;
namespace Logistics.Types.Serials;
internal sealed class SerialOps
{
/// <inheritdoc cref="ISerialService.Delete(PrimaryKeyArgs{long})"/>
public sealed class Delete : ServiceAction<PrimaryKeyArgs<long>>
{
public Delete(IStorageProvider storage, ISerialService serials, IEventService events, ICachingService cache)
{
Storage = storage;
Serials = serials;
Events = events;
Cache = cache;
}
private IStorageProvider Storage { get; }
private ISerialService Serials { get; }
private IEventService Events { get; }
private ICachingService Cache { get; }
protected override async Task OnInvoke()
{
if (await Serials.Select(Arguments.Id) is not ISerial serial)
return;
/*
* Setting state of the entity enable other middleware to use the entity even after it
* is deleted. For example, IEventListener will receive the state of this operation.
*/
SetState(serial);
/*
* Perform delete.
*/
await Storage.Open<Serial>().Update(Arguments.AsEntity<Serial>(State.Deleted));
}
protected override async Task OnCommitted()
{
/*
* Remove entity from the cache.
*/
await Cache.Remove(Serial.EntityKey, Arguments.Id);
/*
* Enqueue event so event listeners can respond to the transaction.
*/
await Events.Enqueue(this, Events, nameof(ISerialService.Deleted), Arguments);
}
}
/// <inheritdoc cref=" ISerialService.Insert(InsertSerialArgs)"/>
public sealed class Insert : ServiceFunction<InsertSerialArgs, long>
{
public Insert(IStorageProvider storage, IEventService events)
{
Storage = storage;
Events = events;
}
private IStorageProvider Storage { get; }
private IEventService Events { get; }
protected override async Task<long> OnInvoke()
{
/*
* Perform insert and return the newly inserted id.
*/
return (await Storage.Open<Serial>().Update(Arguments.AsEntity<Serial>(State.New))).Id;
}
protected override async Task OnCommitted()
{
await Events.Enqueue(this, Events, nameof(ISerialService.Inserted), Arguments);
}
}
/// <inheritdoc cref="ISerialService.Query(QueryArgs?)"/>
public sealed class Query : ServiceFunction<QueryArgs, ImmutableList<ISerial>>
{
public Query(IStorageProvider storage)
{
Storage = storage;
}
private IStorageProvider Storage { get; }
protected override async Task<ImmutableList<ISerial>> OnInvoke()
{
/*
* For non cached entities query always hits the storage.
*/
return await (from e in Storage.Open<Serial>()
select e).WithArguments(Arguments).AsEntities<ISerial>();
}
}
/// <inheritdoc cref="ISerialService.Query(PrimaryKeyListArgs{long})"/>
public sealed class Lookup : ServiceFunction<PrimaryKeyListArgs<long>, ImmutableList<ISerial>>
{
public Lookup(IStorageProvider storage)
{
Storage = storage;
}
private IStorageProvider Storage { get; }
protected override async Task<ImmutableList<ISerial>> OnInvoke()
{
return await (from e in Storage.Open<Serial>()
where Arguments.IdList.Any(f => f == e.Id)
select e).AsEntities<ISerial>();
}
}
/// <inheritdoc cref="ISerialService.Select(PrimaryKeyArgs{long})"/>
public sealed class Select : NullableServiceFunction<PrimaryKeyArgs<long>, ISerial>
{
public Select(IStorageProvider storage, ICachingService cache)
{
Storage = storage;
Cache = cache;
}
private IStorageProvider Storage { get; }
private ICachingService Cache { get; }
protected override async Task<ISerial?> OnInvoke()
{
/*
* First, try to receive entity from the cache. If it doesn't exist in the cache
* load it from storage. If storage returns non null value, store it in the cache
* for subsequent calls. The entity gets remove either because of inactivity or when
* updating or deleting it.
*/
return await Cache.Get<ISerial>(Serial.EntityKey, Arguments.Id, async (f) =>
{
/*
* Doesn't exist in the cache. Let's do the storage action.
*/
return await (from e in Storage.Open<Serial>()
where e.Id == Arguments.Id
select e).AsEntity();
});
}
}
/// <inheritdoc cref="ISerialService.Select(SelectSerialArgs)"/>
public sealed class SelectByValue : NullableServiceFunction<SelectSerialArgs, ISerial>
{
public SelectByValue(IStorageProvider storage, ICachingService cache)
{
Storage = storage;
Cache = cache;
}
private IStorageProvider Storage { get; }
private ICachingService Cache { get; }
protected override async Task<ISerial?> OnInvoke()
{
return await Cache.Get<ISerial>(Serial.EntityKey, f => string.Equals(f.Value, Arguments.Value, StringComparison.OrdinalIgnoreCase), async (f) =>
{
return await (from e in Storage.Open<Serial>()
where string.Equals(e.Value, Arguments.Value, StringComparison.OrdinalIgnoreCase)
select e).AsEntity();
});
}
}
/// <inheritdoc cref="ISerialService.Update(UpdateSerialArgs)"/>
public sealed class Update : ServiceAction<UpdateSerialArgs>
{
public Update(IStorageProvider storage, ICachingService cache, ISerialService packingService, IEventService events)
{
Storage = storage;
Cache = cache;
Serials = packingService;
Events = events;
}
private IStorageProvider Storage { get; }
private ICachingService Cache { get; }
private ISerialService Serials { get; }
private IEventService Events { get; }
protected override async Task OnInvoke()
{
/*
* Set the state of the unchanged entity. This enable other middleware to
* calculate quantity delta, for example because they receive the state of
* this operation. Not all middleware supports this, the primary example of
* such state client is IEventListener.
*/
if (SetState(await Serials.Select(Arguments.Id)) is not Serial entity)
return;
/*
* Sinc ethis is concurrent entity we must perform retry if the concurrency fails.
*/
await Storage.Open<Serial>().Update(entity.Merge(Arguments, State.Default), Arguments, async () =>
{
/*
* The update failed because of concurrency. Remove the entity from the cache to ensure
* it gets loaded from the storage next time with fresh values and try again.
*/
await Cache.Remove(Serial.EntityKey, Arguments.Id);
/*
* Since the entity reloaded we must overwrite its state.
*/
return SetState(await Serials.Select(Arguments.Id)) as Serial;
});
}
protected override async Task OnCommitted()
{
await Cache.Remove(Serial.EntityKey, Arguments.Id);
await Events.Enqueue(this, Serials, nameof(Serials.Updated), Arguments);
}
}
}