The DataSvc is primarily responsible for orchestrating the underlying data access; whilst often one-to-one there may be times that this class will be used to coordinate multiple data access components. It is responsible for the ensuring that the related Entity
is fully constructed/updated/etc. as per the desired operation.
To improve potential performance, and reduce chattiness, within an in-process execution context the DataSvc
introduces a level of caching (short-lived). The cache is managed within the ExecutionContext
via the following: TryGetCacheValue
, CacheSet
, CacheGet
, CacheRemove
, CacheClear
and CacheClearAll
.
The purpose of this cache is to minimise this chattiness to the underlying data source, to reduce this cost, where the time between calls (measured in milliseconds) is such that the data retrieved previously is considered sufficient/valid. This way within an execution context a developer can invoke the XxxDataSvc
multiple times with only a single data source cost.
This logic of getting, setting and clearing the cache is included within the primary Get
, Create
, Update
and Delete
operations only.
To support the goals of an Event-driven architecture an event publish can be included.
An Event
publish is invoked where the eventing infrastructure has been included (configured) during code-generation.
Note: The Event.PublishAsync
does not automatically publish (send) the event out-of-the-box. The Event.Register
must be used to register a function(s) to be invoked, that will in turn perform the work of sending the EventData
message.
Note: This is always performed directly after the primary operation logic such that the event is only published where successful. This is not transactional so if the event publish fails there is no automatic rollback capabilitity. The implementor will need to decide the corrective action for the failure.
This layer is generally code-generated and provides options to provide a fully custom implementation, or has extension opportunities to inject additional logic into the processing pipeline.
The Operation
element within the entity.xml
configuration primarily drives the output. There is a generated class per Entity
named {Entity}DataSvc
.
An end-to-end code-generated processing pipeline generally consists of:
Step | Description |
---|---|
DataSvcInvoker |
The logic is wrapped by a DataSvcInvoker . This enables the BusinessInvokerArgs options to be specified, including DataContextScopeOption , TransactionScopeOption and Exception handler. These values are generally specified in the code-generation configuration. |
Cache |
Trys the cache and returns result where found (as applicable). |
Data |
The {Entity}Data layer is invoked to orchestrate the data processing; this is instantiated by the Factory (enables a test mocking opportunity). |
EventPublish |
Constructs the EventData and invokes the Event.Publish . |
Cache |
Performs a cache set or remove (as applicable). |
OnAfter |
The OnAfter extension opportunity; where set this will be invoked. This enables logic to be invoked after the primary Operation is performed. |
The following demonstrates the usage (a snippet from the sample PersonDataSvc
):
// A Get operation.
public static Task<Person> GetAsync(Guid id)
{
return DataSvcInvoker.Default.InvokeAsync(typeof(PersonDataSvc), async () =>
{
var __key = new UniqueKey(id);
if (ExecutionContext.Current.TryGetCacheValue<Person>(__key, out Person __val))
return __val;
var __result = await Factory.Create<IPersonData>().GetAsync(id);
ExecutionContext.Current.CacheSet<Person>(__key, __result);
if (_getOnAfterAsync != null) await _getOnAfterAsync(__result, id);
return __result;
});
}
// An Update operation.
public static Task<Person> UpdateAsync(Person value)
{
return DataSvcInvoker.Default.InvokeAsync(typeof(PersonDataSvc), async () =>
{
var __result = await Factory.Create<IPersonData>().UpdateAsync(value);
await Beef.Events.Event.PublishAsync(__result, "Demo.Person.{id}", "Update", new KeyValuePair<string, object>("id", __result.Id));
ExecutionContext.Current.CacheSet<Person>(__result?.UniqueKey ?? UniqueKey.Empty, __result);
if (_updateOnAfterAsync != null) await _updateOnAfterAsync(__result);
return __result;
}, new BusinessInvokerArgs { IncludeTransactionScope = true });
}
A custom (OnImplementation
) processing pipeline generally consists of:
Step | Description |
---|---|
DataSvcInvoker |
The logic is wrapped by a DataSvcInvoker . This enables the BusinessInvokerArgs options to be specified, including DataContextScopeOption , TransactionScopeOption and Exception handler. These values are generally specified in the code-generation configuration. |
Cache |
Trys the cache and returns result where found (as applicable). |
OnImplementation |
Invocation of a named XxxxxOnImplementaionAsync method that must be implemented in a non-generated partial class. |
EventPublish |
Constructs the EventData and invokes the Event.Publish . |
Cache |
Performs a cache set or remove (as applicable). |
OnAfter |
The OnAfter extension opportunity; where set this will be invoked. This enables logic to be invoked after the primary Operation is performed. |
The following demonstrates the usage:
public static Task<Person> UpdateAsync(Person value)
{
return DataSvcInvoker.Default.InvokeAsync(typeof(PersonDataSvc), async () =>
{
var __result = await UpdateOnImplementationAsync(value);
await Beef.Events.Event.PublishAsync(__result, "Demo.Person.{id}", "Update", new KeyValuePair<string, object>("id", __result.Id));
ExecutionContext.Current.CacheSet<Person>(__result?.UniqueKey ?? UniqueKey.Empty, __result);
if (_updateOnAfterAsync != null) await _updateOnAfterAsync(__result);
return __result;
}, new BusinessInvokerArgs { IncludeTransactionScope = true });
}