Skip to content

Commit

Permalink
Add more samples & examples
Browse files Browse the repository at this point in the history
  • Loading branch information
inputfalken committed Sep 22, 2024
1 parent bf7756c commit 21d41b3
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 98 deletions.
14 changes: 14 additions & 0 deletions Dynatello.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{1057
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "samples\Repository\Repository.csproj", "{41B5DF1A-1542-4CBF-BEB0-CE684D054363}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtendDto", "samples\ExtendDto\ExtendDto.csproj", "{30A5C236-1D22-47E4-9BEA-1F4ED3167830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RequestPipeline", "samples\RequestPipeline\RequestPipeline.csproj", "{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -36,10 +40,20 @@ Global
{41B5DF1A-1542-4CBF-BEB0-CE684D054363}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41B5DF1A-1542-4CBF-BEB0-CE684D054363}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41B5DF1A-1542-4CBF-BEB0-CE684D054363}.Release|Any CPU.Build.0 = Release|Any CPU
{30A5C236-1D22-47E4-9BEA-1F4ED3167830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30A5C236-1D22-47E4-9BEA-1F4ED3167830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30A5C236-1D22-47E4-9BEA-1F4ED3167830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30A5C236-1D22-47E4-9BEA-1F4ED3167830}.Release|Any CPU.Build.0 = Release|Any CPU
{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{892C771D-B39C-4221-B04D-FF59A061995C} = {EDABF10C-5499-442F-8BE1-9205EEE7E016}
{D0078AB2-F320-42BD-89C9-ED5491A35DE1} = {F0497192-D217-4A90-9C1C-E1A6DEDC4526}
{41B5DF1A-1542-4CBF-BEB0-CE684D054363} = {10579316-5B7C-49A6-8128-54F95D2BBE5C}
{30A5C236-1D22-47E4-9BEA-1F4ED3167830} = {10579316-5B7C-49A6-8128-54F95D2BBE5C}
{FF35AE29-0089-4D12-A6A8-F3ADC5E19F25} = {10579316-5B7C-49A6-8128-54F95D2BBE5C}
EndGlobalSection
EndGlobal
161 changes: 112 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Dynatello

## What does it do?
A reflection free & generic typed solution to send requests towards dynamodb without having to manually maintain the low-level parts.
## What does the library do?

Offers a unified API ontop of the low-level API in DynamoDB by utilizing [DynamoDB.SourceGenerator](https://github.com/inputfalken/DynamoDB.SourceGenerator).

## Features

Expand All @@ -19,22 +20,116 @@ Add the following NuGet package as a dependency to you project.
[2]: https://www.nuget.org/packages/Dynatello

## Example
The examples are taken from [samples] (samples/) directory.

### Extend DTO's
How you could extend a DTO to contain the mashaller functionality.

```csharp
using DynamoDBGenerator.Attributes;
using Dynatello;
using Dynatello.Builders;
using Dynatello.Handlers;

IRequestHandler<string, Cat?> getById = Cat
.FromId.OnTable("TABLE")
.ToGetRequestHandler(x => x.ToGetRequestBuilder());

if (args.Length == 1)
{
var response = await getById.Send(args[0], CancellationToken.None);

Console.WriteLine(response);
}
else
{
throw new NotImplementedException();
}

[DynamoDBMarshaller(AccessName = "FromId", ArgumentType = typeof(string))]
public partial record Cat(string Id, string Name, double Cuteness);
```


### Middlware through `IRequestPipeline`.

```csharp
using Amazon.Runtime;
using DynamoDBGenerator.Attributes;
using Dynatello;
using Dynatello.Builders;
using Dynatello.Handlers;
using Dynatello.Pipelines;

IRequestHandler<string, Cat?> getById = Cat
.FromId.OnTable("TABLE")
.ToGetRequestHandler(
x => x.ToGetRequestBuilder(),
x =>
{
x.RequestsPipelines.Add(new RequestConsoleLogger());
}
);

if (args.Length == 1)
{
var response = await getById.Send(args[0], CancellationToken.None);

Console.WriteLine(response);
}
else
{
throw new NotImplementedException();
}

public class RequestConsoleLogger : IRequestPipeLine
{
public Task<AmazonWebServiceResponse> Invoke(
RequestContext requestContext,
Func<RequestContext, Task<AmazonWebServiceResponse>> continuation
)
{
throw new NotImplementedException();
}
}

[DynamoDBMarshaller(AccessName = "FromId", ArgumentType = typeof(string))]
public partial record Cat(string Id, string Name, double Cuteness);
```


### Repository
How you could isolate all DynamoDB access code to a repository class.

```csharp
using System.Diagnostics;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.Runtime;
using DynamoDBGenerator.Attributes;
using Dynatello;
using Dynatello.Builders;
using Dynatello.Builders.Types;
using Dynatello.Handlers;
using Dynatello.Pipelines;

ProductRepository productRepository = new ProductRepository("PRODUCTS", new AmazonDynamoDBClient());

public class ProductRepository
// These attributes is what makes the source generator kick in. Make sure to have the class 'partial' as well.
[DynamoDBMarshaller(EntityType = typeof(Product), AccessName = "FromProduct")]
[DynamoDBMarshaller(
EntityType = typeof(Product),
AccessName = "FromId",
ArgumentType = typeof(string)
)]
[DynamoDBMarshaller(
EntityType = typeof(Product),
AccessName = "FromUpdatePricePayload",
ArgumentType = typeof((string Id, decimal NewPrice, DateTime TimeStamp))
)]
[DynamoDBMarshaller(
EntityType = typeof(Product),
AccessName = "FromPrice",
ArgumentType = typeof(decimal)
)]
public partial class ProductRepository
{
private readonly IRequestHandler<string, Product?> _getById;
private readonly IRequestHandler<
Expand All @@ -45,45 +140,27 @@ public class ProductRepository
private readonly IRequestHandler<decimal, IReadOnlyList<Product>> _queryByPrice;
private readonly IRequestHandler<string, Product?> _deleteById;

private class RequestLogger : IRequestPipeLine
{
public async Task<AmazonWebServiceResponse> Invoke(
RequestContext requestContext,
Func<RequestContext, Task<AmazonWebServiceResponse>> continuation
)
{
var stopwatch = Stopwatch.StartNew();
Console.WriteLine("Starting request");
var request = await continuation(requestContext);
Console.WriteLine($"Request finished after '{stopwatch.Elapsed}'.");
return request;
}
}

public ProductRepository(string tableName, IAmazonDynamoDB amazonDynamoDb)
{
var requestLogger = new RequestLogger();
_getById = Product
.FromId.OnTable(tableName)
_getById = FromId
.OnTable(tableName)
.ToGetRequestHandler(
x => x.ToGetRequestBuilder(),
x =>
{
x.AmazonDynamoDB = amazonDynamoDb;
// All handlers comes with the ability to add middlewares that will be executed.
x.RequestsPipelines.Add(requestLogger);
}
);

_deleteById = Product
.FromId.OnTable(tableName)
_deleteById = FromId
.OnTable(tableName)
.ToDeleteRequestHandler(
x => x.ToDeleteRequestBuilder(),
x => x.AmazonDynamoDB = amazonDynamoDb
);

_updatePrice = Product
.FromUpdatePricePayload.OnTable(tableName)
_updatePrice = FromUpdatePricePayload
.OnTable(tableName)
.ToUpdateRequestHandler(
x =>
x.WithUpdateExpression(
Expand All @@ -96,17 +173,17 @@ public class ProductRepository
x => x.AmazonDynamoDB = amazonDynamoDb
);

_createProduct = Product
.FromProduct.OnTable(tableName)
_createProduct = FromProduct
.OnTable(tableName)
.ToPutRequestHandler(
x =>
x.WithConditionExpression((db, arg) => $"{db.Id} <> {arg.Id}") // Ensure we don't have an existing Product in DynamoDB
.ToPutRequestBuilder(),
x => x.AmazonDynamoDB = amazonDynamoDb
);

_queryByPrice = Product
.FromPrice.OnTable(tableName)
_queryByPrice = FromPrice
.OnTable(tableName)
.ToQueryRequestHandler(
x =>
x.WithKeyConditionExpression((db, arg) => $"{db.Price} = {arg}")
Expand All @@ -116,12 +193,6 @@ public class ProductRepository
},
x => x.AmazonDynamoDB = amazonDynamoDb
);

// You can also use a RequestBuilder if you want to handle the response yourself.
GetRequestBuilder<string> getProductByIdRequestBuilder = Product
.FromId.OnTable(tableName)
.ToRequestBuilderFactory()
.ToGetRequestBuilder();
}

public Task<IReadOnlyList<Product>> SearchByPrice(decimal price) =>
Expand All @@ -137,15 +208,7 @@ public class ProductRepository
_updatePrice.Send((id, price, DateTime.UtcNow), default);
}

// These attributes is what makes the source generator kick in. Make sure to have the class 'partial' as well.
[DynamoDBMarshaller(AccessName = "FromProduct")]
[DynamoDBMarshaller(AccessName = "FromId", ArgumentType = typeof(string))]
[DynamoDBMarshaller(
AccessName = "FromUpdatePricePayload",
ArgumentType = typeof((string Id, decimal NewPrice, DateTime TimeStamp))
)]
[DynamoDBMarshaller(AccessName = "FromPrice", ArgumentType = typeof(decimal))]
public partial record Product(
public record Product(
[property: DynamoDBHashKey, DynamoDBGlobalSecondaryIndexRangeKey(Product.PriceIndex)] string Id,
[property: DynamoDBGlobalSecondaryIndexHashKey(Product.PriceIndex)] decimal Price,
string Description,
Expand Down
14 changes: 14 additions & 0 deletions samples/ExtendDto/ExtendDto.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Dynatello\Dynatello.csproj" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions samples/ExtendDto/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using DynamoDBGenerator.Attributes;
using Dynatello;
using Dynatello.Builders;
using Dynatello.Handlers;

IRequestHandler<string, Cat?> getById = Cat
.FromId.OnTable("TABLE")
.ToGetRequestHandler(x => x.ToGetRequestBuilder());

if (args.Length == 1)
{
var response = await getById.Send(args[0], CancellationToken.None);

Console.WriteLine(response);
}
else
{
throw new NotImplementedException();
}

[DynamoDBMarshaller(AccessName = "FromId", ArgumentType = typeof(string))]
public partial record Cat(string Id, string Name, double Cuteness);
Loading

0 comments on commit 21d41b3

Please sign in to comment.