Skip to content

Latest commit

 

History

History
105 lines (70 loc) · 7.34 KB

Layer-Data.md

File metadata and controls

105 lines (70 loc) · 7.34 KB

Data (Data access)

The Data layer is primarily responsible for performing the persistence CRUD (Create, Read, Update or Delete) and Query requests, handling the underlying wire transport, protocols and payload to enable.

This is the area in which a developer using Beef is likely to spend most of their time. The basic CRUD capabilities are enabled mostly out-of-the-box; however, the data access logic complexity will likely exceed this as the complexity of the requirements increase.


Supported

There are CoreEx capabilities to encapsulate the data access in a largely consistent manner; in that they largely support the following similar patterns for CRUD and Query (including paging).

The following data access capabilities are currently supported; as well as being integrated into the Beef code-generation:

Assembly Description
CoreEx.Database ADO.NET using the likes of Stored Procedures and inline SQL.
CoreEx.EntityFrameworkCore Entity Framework (EF) Core framework.
CoreEx.Cosmos Cosmos DB execution framework.
CoreEx.Http HTTP endpoint invocation.

This obviously does not prohibit access to other data sources; just that these will need to be implemented in a fully custom manner.


Usage

This layer is generally code-generated and provides options to provide a fully custom implementation, and has extension opportunities to inject additional logic into the processing pipeline.

The Operation element within the entity.beef-5.yaml configuration primarily drives the output

There is a generated class per Entity named {Entity}Data. There is also a corresonding interface named I{Entity}Data generated so the likes of test mocking etc. can be employed. For example, if the entity is named Person, there will be corresponding PersonData and IPersonData classes.


Railway-oriented programming

CoreEx version 3.0.0 introduced monadic error-handling, often referred to as Railway-oriented programming. This is enabled via the key types of Result and Result<T>; please review the corresponding documentation for more detail on purpose and usage.

The Result and Result<T> have been integrated into the code-generated output and is leveraged within the underlying validation. This is intended to simplify success and failure tracking, avoiding the need, and performance cost, in throwing resulting exceptions.

This is implemented by default; however, can be disabled by setting the useResult attribute to false within the code-generation configuration.


Code-generated

An end-to-end code-generated processing pipeline generally consists of:

Step Description
DataInvoker The logic is wrapped by a DataInvoker. This enables the InvokerArgs options to be specified, including TransactionScopeOption and Exception handler. These values are generally specified in the code-generation configuration.
OnBefore The OnBefore extension opportunity; where set this will be invoked. This enables logic to be invoked before the primary Operation is performed.
Operation The actual operation execution (Get, Create, Update, Delete and Query). This will include an OnQuery extension opportunity where performing a Query.
OnAfter The OnAfter extension opportunity; where set this will be invoked. This enables logic to be invoked after the primary Operation is performed.
OnException The OnException extension opportunity; where set this will be invoked to handle any unhandled exceptions.

† Note: To minimize the generated code the extension opportunities are only generated where selected. This is performed by setting the dataExtensions attribute to true within the Entity code-generation configuration.

The following demonstrates the generated code (a snippet from the sample PersonData) that invokes a stored procedure that does not include DataExtensions:

public Task<Result<Person?> GetAsync(Guid id)
{
    return _db.StoredProcedure("[Demo].[spPersonGet]").GetAsync(DbMapper.Default, id);
}

The following demonstrates the generated code (a snippet from the sample EmployeeData) that leverages Entity Framework that does not include DataExtensions:

public Task<EmployeeBaseCollectionResult> GetByArgsAsync(EmployeeArgs? args, PagingArgs? paging)
{
    return _ef.Query<EmployeeBase, EfModel.Employee>(q => _getByArgsOnQuery?.Invoke(q, args) ?? q).WithPaging(paging).SelectResultAsync<EmployeeBaseCollectionResult, EmployeeBaseCollection>();
}

The following demonstrates the generated code (a snippet from the sample PersonData) that includes DataExtensions:

public Task<Person?> GetWithEfAsync(Guid id) => DataInvoker.Current.InvokeAsync(this, async _ => 
{
    await Invoker.InvokeAsync(_getWithEfOnBeforeAsync?.Invoke(id)).ConfigureAwait(false);
    var __result = await _ef.GetAsync<Person, EfModel.Person>(id).ConfigureAwait(false);
    await Invoker.InvokeAsync(_getWithEfOnAfterAsync?.Invoke(__result, id)).ConfigureAwait(false);
    return __result;
}, new InvokerArgs { ExceptionHandler = _getWithEfOnException });

Custom

A custom (OnImplementation) processing pipeline generally consists of:

Step Description
DataInvoker The logic is wrapped by a DataInvoker. This enables the InvokerArgs options to be specified, including TransactionScopeOption and Exception handler. These values are generally specified in the code-generation configuration.
OnImplementation Invocation of a named XxxOnImplementaionAsync method that must be implemented in a non-generated partial class.

The following demonstrates the usage (a snippet from the sample PersonData):

public Task<PersonDetailCollectionResult> GetDetailByArgsAsync(PersonArgs? args, PagingArgs? paging) => GetDetailByArgsOnImplementationAsync(args, paging);