Skip to content

Commit

Permalink
Entity Manager supporting explicit CancellationToken passed as argume…
Browse files Browse the repository at this point in the history
…nt; HttpRequest cancellation source; Dynamic LINQ filters to Entity Manager
  • Loading branch information
tsutomi committed Oct 9, 2023
1 parent ee0a6c7 commit da923d1
Show file tree
Hide file tree
Showing 17 changed files with 639 additions and 75 deletions.
49 changes: 49 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Contributing to Deveel Repository

We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:

- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer

## We Develop with Github
We use github to host code, to track issues and feature requests, as well as accept pull requests.

## GitHub Flow

We use the [Github Flow](https://guides.github.com/introduction/flow/index.html) that basically means that all code changes happen through pull requests.

Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests:

1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!

## License of Your Contributions

In short, when you submit code changes, your submissions are understood to be under the same [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0) that covers the project. Feel free to contact the maintainers if that's a concern.

## Reporting Bugs

We use GitHub [issues](https://github.com/deveel/deveel.repository/issues) to track public bugs. Report a bug by [opening a new issue](); it's that easy!

### Write bug reports with detail, background, and sample code

[This is an example](http://stackoverflow.com/q/12488905/180626) of a bug report I wrote, and I think it's not a bad model. Here's [another example from Craig Hockenberry](http://www.openradar.me/11905408), an app developer whom I greatly respect.

**Great Bug Reports** tend to have:

- A quick summary and/or background
- Steps to reproduce
- Be specific!
- Give sample code if you can. [My stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)

People *love* thorough bug reports. I'm not even kidding.
11 changes: 9 additions & 2 deletions Deveel.Repository.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Deveel.Repository.licenseheader = Deveel.Repository.licenseheader
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.Manager", "src\Deveel.Repository.Manager\Deveel.Repository.Manager.csproj", "{C1029341-6EC0-4D83-AC9B-D4DA686237B4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.Manager", "src\Deveel.Repository.Manager\Deveel.Repository.Manager.csproj", "{C1029341-6EC0-4D83-AC9B-D4DA686237B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.Manager.XUnit", "test\Deveel.Repository.Manager.XUnit\Deveel.Repository.Manager.XUnit.csproj", "{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.Manager.XUnit", "test\Deveel.Repository.Manager.XUnit\Deveel.Repository.Manager.XUnit.csproj", "{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.Manager.DynamicLinq", "src\Deveel.Repository.Manager.DynamicLinq\Deveel.Repository.Manager.DynamicLinq.csproj", "{638851EF-B000-490C-9035-A962279A3E9B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -108,6 +110,10 @@ Global
{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9}.Release|Any CPU.Build.0 = Release|Any CPU
{638851EF-B000-490C-9035-A962279A3E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{638851EF-B000-490C-9035-A962279A3E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{638851EF-B000-490C-9035-A962279A3E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{638851EF-B000-490C-9035-A962279A3E9B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -127,6 +133,7 @@ Global
{201917F4-0557-46AE-B532-A4CC801AF5A7} = {50434E05-0F21-4871-AFB3-A483CEE4A300}
{C1029341-6EC0-4D83-AC9B-D4DA686237B4} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
{B63DFEE8-7964-4DC3-9DD5-8E11319B8EA9} = {50434E05-0F21-4871-AFB3-A483CEE4A300}
{638851EF-B000-490C-9035-A962279A3E9B} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01FD9B16-84B3-4D99-80C1-11B2F3D65B56}
Expand Down
54 changes: 51 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[![Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Repository CI/CD](https://github.com/deveel/deveel.repository/actions/workflows/ci.yml/badge.svg)](https://github.com/deveel/deveel.repository/actions/workflows/ci.yml)
[![Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Repository CI/CD](https://github.com/deveel/deveel.repository/actions/workflows/ci.yml/badge.svg)](https://github.com/deveel/deveel.repository/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/deveel/deveel.repository/graph/badge.svg?token=5US7L3C7ES)](https://codecov.io/gh/deveel/deveel.repository)

# Deveel Repository

This project wants to provide a _low-ambitions_ / _low-expectations_ implementation of the (_infamous_) _[Repository Pattern](https://martinfowler.com/eaaCatalog/repository.html)_ for .NET to support the development of applications that need to access different data sources, using a common interface, respecting the principles of the _[Domain-Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)_ and the _[SOLID](https://en.wikipedia.org/wiki/SOLID)_ principles.
Expand Down Expand Up @@ -34,6 +35,7 @@ The framework is based on a _kernel_ package, that provides the basic interfaces
| _Deveel.Repository.MongoDb_ | [![NuGet](https://img.shields.io/nuget/v/Deveel.Repository.MongoDb.svg)](https://www.nuget.org/packages/Deveel.Repository.MongoDb/) |
| _Deveel.Repository.EntityFramework_ | [![NuGet](https://img.shields.io/nuget/v/Deveel.Repository.EntityFramework.svg)](https://www.nuget.org/packages/Deveel.Repository.EntityFramework/) |
| _Deveel.Repository.DynamicLinq_ | [![NuGet](https://img.shields.io/nuget/v/Deveel.Repository.DynamicLinq.svg)](https://www.nuget.org/packages/Deveel.Repository.DynamicLinq/) |
| _Deveel.Repository.Manager_ | [![NuGet](https://img.shields.io/nuget/v/Deveel.Repository.Manager.svg)](https://www.nuget.org/packages/Deveel.Repository.Manager/) |

### The Kernel Package

Expand All @@ -57,7 +59,7 @@ The library provides a set of drivers to access different data sources, that can

| Driver | Package | Description |
| ------ | ------- | ----------- |
| _In-Memory_ | `Deveel.Repository.InMemory` | An implementation of the repository pattern that stores the data in-memory. |
| _In-Memory_ | `Deveel.Repository.InMemory` | A very simple implementation of the repository pattern that stores the data in-memory. |
| _MongoDB_ | `Deveel.Repository.MongoDb` | An implementation of the repository pattern that stores the data in a MongoDB database. |
| _Entity Framework Core_ | `Deveel.Repository.EntityFramework` | An implementation of the repository pattern that stores the data in a relational database, using the [Entity Framework Core](https://github.com/dotnet/efcore). |

Expand Down Expand Up @@ -85,6 +87,8 @@ public void ConfigureServices(IServiceCollection services) {

The type of the argument of the method is not the type of the entity, but the type of the repository: the library will use reflection to scan the type itself and find all the generic arguments of the `IRepository<TEntity>` interface, and register the repository in the dependency injection container.

### Consuming the Repository

In fact, after that exmaple call above, you will have the following services available to be injected in your application:

| Service | Description |
Expand Down Expand Up @@ -348,4 +352,48 @@ services.AddEntityRepository<MyEntity>();

#### Filtering Data

The `EntityRepository<TEntity>` implements both the `IQueryableRepository<TEntity>` and the `IFilterableRepository<TEntity>` interfaces, and allows to query the data only through the `ExpressionFilter<TEntity>` class or through lambda expressions of type `Expression<Func<TEntity, bool>>`.
The `EntityRepository<TEntity>` implements both the `IQueryableRepository<TEntity>` and the `IFilterableRepository<TEntity>` interfaces, and allows to query the data only through the `ExpressionFilter<TEntity>` class or through lambda expressions of type `Expression<Func<TEntity, bool>>`.

## Entity Manager

The framework provides an extension that allows to control the operations performed on the repository, ensuring the consistency of the data (through validation).

The `EntityManager<TEntity>` class wraps around instances of `IRepository<TEntity>`, enriching the basic operations with validation logic, and providing a way to intercept the operations performed on the repository, and preventing exceptions to be thrown without a proper handling.

It is possible to derive from the `EntityManager<TEntity>` class to implement your own business and validation logic, and to intercept the operations performed on the repository.

This class is suited to be used in application contexts, where a higher level of control is required on the operations performed on the repository (such for example in the case of ASP.NET services).

### Instrumentation

To register an instance of `EntityManager<TEntity>` in the dependency injection container, you can use the `AddEntityManager<TManager>` extension method of the `IServiceCollection` interface.

```csharp
public void ConfigureServices(IServiceCollection services) {
services.AddEntityManager<MyEntityManager>();
}
```

The method will register an instance of `MyEntityManager` and `EntityManager<TEntity>` in the dependency injection container, ready to be used.

### Entity Validation

It is possible to validate the entities before they are added or updated in the repository, by implementing the `IEntityValidator<TEntity>` interface, and registering an instance of the validator in the dependency injection container.

The `EntityManager<TEntity>` class will check for instances of `IEntityValidator<TEntity>` in the dependency injection container, and will use the first instance found to validate the entities before they are added or updated in the repository.

### Operation Cancellation

The `EntityManager<TEntity>` class provides a way to directly cancel the operations performed on the repository, by passing an argument of type `CancellationToken` to each asynchronous operation, and optionally verifies for instances of `IOperationCancellationSource` that are registered in the dependency injection container.

When the `CancellationToken` argument of an operation is `null`, the `EntityManager<TEntity>` class will check for instances of `IOperationCancellationSource` in the dependency injection container, and will use the first instance found to cancel the operation.

The value of this approach is to be able to attach the cancellation of the operation to a specific context (such as `HttpContext`), and to be able to cancel the operation from a different context (for instance when the HTTP request is cancelled).

## License

The project is licensed under the terms of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).

## Contributing

The project is open to contributions: if you want to contribute to the project, please read the [contributing guidelines](CONTRIBUTING.md) for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2023 Deveel AS
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Deveel.Data {
/// <summary>
/// Extends the <see cref="EntityManager{TEntity}"/> to provide
/// filtering capabilities using a dynamic LINQ expression.
/// </summary>
/// <seealso cref="DynamicLinqFilter"/>
public static class EntityManagerExtensions {
/// <summary>
/// Finds the first entity in the repository that matches the given
/// dynamic LINQ expression.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity to find.
/// </typeparam>
/// <param name="manager">
/// The entity manager to use to find the entity.
/// </param>
/// <param name="expression">
/// The dynamic LINQ expression to use to filter the entity.
/// </param>
/// <param name="cancellationToken">
/// A token to cancel the operation.
/// </param>
/// <returns>
/// Returns the first entity that matches the given expression, or
/// <c>null</c> if no entity was found.
/// </returns>
public static Task<TEntity?> FindFirstAsync<TEntity>(this EntityManager<TEntity> manager, string expression, CancellationToken? cancellationToken = null)
where TEntity : class
=> manager.FindFirstAsync(new DynamicLinqFilter(expression), cancellationToken);

/// <summary>
/// Finds a range of entities in the repository that matches the given
/// dynamic LINQ expression.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity to find.
/// </typeparam>
/// <param name="manager">
/// The entity manager to use to find the entity.
/// </param>
/// <param name="expression">
/// The dynamic LINQ expression to use to filter the entity.
/// </param>
/// <param name="cancellationToken">
/// A token to cancel the operation.
/// </param>
/// <returns>
/// Returns a list of entities that matches the given expression.
/// </returns>
public static Task<IList<TEntity>> FindAllAsync<TEntity>(this EntityManager<TEntity> manager, string expression, CancellationToken? cancellationToken = null)
where TEntity : class
=> manager.FindAllAsync(new DynamicLinqFilter(expression), cancellationToken);

/// <summary>
/// Counts the number of entities in the repository that matches the
/// dynamic LINQ expression.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity to count.
/// </typeparam>
/// <param name="manager">
/// The entity manager to use to count the entities.
/// </param>
/// <param name="expression">
/// The dynamic LINQ expression to use to filter the entity.
/// </param>
/// <param name="cancellationToken">
/// A token to cancel the operation.
/// </param>
/// <returns>
/// Returns the number of entities that matches the given expression.
/// </returns>
public static Task<long> CountAsync<TEntity>(this EntityManager<TEntity> manager, string expression, CancellationToken? cancellationToken = null)
where TEntity : class
=> manager.CountAsync(new DynamicLinqFilter(expression), cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Deveel.Repository.DynamicLinq\Deveel.Repository.DynamicLinq.csproj" />
<ProjectReference Include="..\Deveel.Repository.Manager\Deveel.Repository.Manager.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit da923d1

Please sign in to comment.