Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new assembly with an abstraction layer with the source generated code #71

Merged
merged 16 commits into from
Mar 26, 2024
16 changes: 15 additions & 1 deletion DynamoDBGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBGenerator.SourceGenerator", "src\DynamoDBGenerator.SourceGenerator\DynamoDBGenerator.SourceGenerator.csproj", "{648B1DF4-9684-4422-95F5-74BB89862E4D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "samples\ConsoleApp\ConsoleApp.csproj", "{834A7F6C-3C82-427F-9BFB-9686672A5BDE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynatelloRepository", "samples\DynatelloRepository\DynatelloRepository.csproj", "{834A7F6C-3C82-427F-9BFB-9686672A5BDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBGenerator", "src\DynamoDBGenerator\DynamoDBGenerator.csproj", "{53F899A8-28AA-450F-9C62-FD478119B2B7}"
EndProject
Expand All @@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{CF34
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{11F1D954-39EC-4EDD-9460-04FCC216E97A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynatello.Tests", "tests\Dynatello.Tests\Dynatello.Tests.csproj", "{D9C9C74E-B52C-4510-9258-BA78532EAABB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynatello", "src\Dynatello\Dynatello.csproj", "{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -42,12 +46,22 @@ Global
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Release|Any CPU.Build.0 = Release|Any CPU
{D9C9C74E-B52C-4510-9258-BA78532EAABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9C9C74E-B52C-4510-9258-BA78532EAABB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9C9C74E-B52C-4510-9258-BA78532EAABB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9C9C74E-B52C-4510-9258-BA78532EAABB}.Release|Any CPU.Build.0 = Release|Any CPU
{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D8EABA41-D014-49BD-B109-54829DB835E7} = {E84C4630-5241-4FAA-8F86-964AB25A2C6F}
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944} = {E84C4630-5241-4FAA-8F86-964AB25A2C6F}
{834A7F6C-3C82-427F-9BFB-9686672A5BDE} = {CF34190F-AC01-42FC-B28A-DD820442243A}
{53F899A8-28AA-450F-9C62-FD478119B2B7} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
{648B1DF4-9684-4422-95F5-74BB89862E4D} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
{D9C9C74E-B52C-4510-9258-BA78532EAABB} = {E84C4630-5241-4FAA-8F86-964AB25A2C6F}
{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
EndGlobalSection
EndGlobal
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ This source generator is crafted to simplify DynamoDB integration for your proje
This project has not been tested in any real scenario and currently serves as a hobby project.

## Installation
Install the following packages from Nuget:
Theres two way to install this project either by the using `Dynatello` or by `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator`.

* `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator` is the base functionality where you get reusable marshaller that's source generated.
* Dyntello extends the marshaller in order to create reusable generic request builders. See this [example](https://github.com/inputfalken/DynamoDB.SourceGenerator/blob/main/samples/DynatelloRepository/Program.cs).
* NOTE: If you install Dynatello, you do not need to specify `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator` as dependencies.

[![DynamoDBGenerator][5]][6]

---

[![DynamoDBGenerator][1]][2]

Expand All @@ -16,6 +24,8 @@ Install the following packages from Nuget:
[2]: https://www.nuget.org/packages/DynamoDBGenerator
[3]: https://img.shields.io/nuget/v/DynamoDBGenerator.SourceGenerator.svg?label=DynamoDBGenerator.SourceGenerator
[4]: https://www.nuget.org/packages/DynamoDBGenerator.SourceGenerator
[5]: https://img.shields.io/nuget/v/Dynatello.svg?label=Dynatello
[6]: https://www.nuget.org/packages/Dynatello

The `DynamoDBGenerator.SourceGenerator` is where the source generator is implemented.
The source generator will look for attributes and implement interfaces that exists in `DynamoDBGenerator`.
Expand Down
11 changes: 0 additions & 11 deletions samples/ConsoleApp/Program.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DynamoDBGenerator\DynamoDBGenerator.csproj" />
<ProjectReference Include="..\..\src\DynamoDBGenerator.SourceGenerator\DynamoDBGenerator.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\Dynatello\Dynatello.csproj" />
</ItemGroup>


Expand Down
114 changes: 114 additions & 0 deletions samples/DynatelloRepository/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Net;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.Model;
using DynamoDBGenerator.Attributes;
using Dynatello;
using Dynatello.Builders;
using Dynatello.Builders.Types;

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

public class ProductRepository
{
private readonly IAmazonDynamoDB _amazonDynamoDb;
private readonly GetRequestBuilder<string> _getProductByTable;
private readonly UpdateRequestBuilder<(string Id, decimal NewPrice, DateTime TimeStamp)> _updatePrice;
private readonly PutRequestBuilder<Product> _createProduct;
private readonly QueryRequestBuilder<decimal> _queryByPrice;

public ProductRepository(string tableName, IAmazonDynamoDB amazonDynamoDb)
{
_amazonDynamoDb = amazonDynamoDb;

_getProductByTable = Product.GetById
.OnTable(tableName)
.ToGetRequestBuilder(arg => arg); // Since the ArgumentType is set to string, we don't need to select a property.

_updatePrice = Product.UpdatePrice
.OnTable(tableName)
.WithUpdateExpression((db, arg) => $"SET {db.Price} = {arg.NewPrice}, {db.Metadata.ModifiedAt} = {arg.TimeStamp}") // Specify the update operation
.ToUpdateItemRequestBuilder((marshaller, arg) => marshaller.PartitionKey(arg.Id));

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

_queryByPrice = Product.QueryByPrice
.OnTable(tableName)
.WithKeyConditionExpression((db, arg) => $"{db.Price} = {arg}")
.ToQueryRequestBuilder()
with
{
IndexName = Product.PriceIndex
};
}

public async Task<IReadOnlyList<Product>> SearchByPrice(decimal price)
{
QueryRequest request = _queryByPrice.Build(price);
QueryResponse? response = await _amazonDynamoDb.QueryAsync(request);

if (response.HttpStatusCode is not HttpStatusCode.OK)
throw new Exception("...");

return response.Items
.Select(x => Product.QueryByPrice.Unmarshall(x))
.ToArray();
}

public async Task Create(Product product)
{
PutItemRequest request = _createProduct.Build(product);
PutItemResponse response = await _amazonDynamoDb.PutItemAsync(request);

if (response.HttpStatusCode is not HttpStatusCode.OK)
throw new Exception("...");
}

public async Task<Product?> GetById(string id)
{
GetItemRequest request = _getProductByTable.Build(id);
GetItemResponse response = await _amazonDynamoDb.GetItemAsync(request);

if (response.HttpStatusCode is HttpStatusCode.NotFound)
return null;

if (response.HttpStatusCode is not HttpStatusCode.OK)
throw new Exception("...");

Product product = Product.GetById.Unmarshall(response.Item);

return product;
}

public async Task<Product?> UpdatePrice(string id, decimal price)
{
UpdateItemRequest request = _updatePrice.Build((id, price, DateTime.UtcNow));
UpdateItemResponse response = await _amazonDynamoDb.UpdateItemAsync(request);

if (response.HttpStatusCode is not HttpStatusCode.OK)
return null;

Product product = Product.UpdatePrice.Unmarshall(response.Attributes);

return product;
}
}

[DynamoDBMarshaller(typeof(Product), PropertyName = "Put")]
[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof(string), PropertyName = "GetById")]
[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof((string Id, decimal NewPrice, DateTime TimeStamp)), PropertyName = "UpdatePrice")]
[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof(decimal), PropertyName = "QueryByPrice")]
public partial record Product(
[property: DynamoDBHashKey, DynamoDBGlobalSecondaryIndexRangeKey(Product.PriceIndex)] string Id,
[property: DynamoDBGlobalSecondaryIndexHashKey(Product.PriceIndex)] decimal Price,
string Description,
Product.MetadataEntity Metadata
)
{
public const string PriceIndex = "PriceIndex";

public record MetadataEntity(DateTime CreatedAt, DateTime ModifiedAt);
}
86 changes: 1 addition & 85 deletions src/DynamoDBGenerator/Extensions/DynamoDBMarshallerExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using DynamoDBGenerator.Internal;

namespace DynamoDBGenerator.Extensions;

/// <summary>
Expand Down Expand Up @@ -74,86 +72,4 @@ params Func<TReferences, TArgumentReferences, string>[] expressionBuilders
expressionBuilders
);
}

/// <summary>
/// Converts the <see cref="IDynamoDBMarshaller{TEntity,TArg,TEntityAttributeNameTracker,TArgumentAttributeValueTracker}"/> into an <see cref="IDynamoDBClient{TEntity,TArgument,TReferences,TArgumentReferences}"/>.
/// </summary>
public static IDynamoDBClient<T, TArg, TReferences, TArgumentReferences> ToDynamoDBClient<T, TArg, TReferences, TArgumentReferences>(
this IDynamoDBMarshaller<T, TArg, TReferences, TArgumentReferences> item,
string tableName,
IAmazonDynamoDB dynamoDB
)
where TReferences : IAttributeExpressionNameTracker
where TArgumentReferences : IAttributeExpressionValueTracker<TArg>
{
return new DynamoDBClient<T, TArg, TReferences, TArgumentReferences>(item, tableName, dynamoDB);
}

/// <summary>
/// Creates a <see cref="PutItemRequest"/>.
/// </summary>
public static PutItemRequest ToPutItemRequest<T, TArg, TReferences, TArgumentReferences>(
this IDynamoDBMarshaller<T, TArg, TReferences, TArgumentReferences> item,
T entity,
ReturnValue returnValue,
string tableName
)
where TReferences : IAttributeExpressionNameTracker
where TArgumentReferences : IAttributeExpressionValueTracker<TArg>
where T : TArg
{
return item.ToPutItemRequestInternal(entity, entity, null, returnValue, tableName);
}

/// <summary>
/// Creates a <see cref="PutItemRequest"/> with condition expression.
/// </summary>
public static PutItemRequest ToPutItemRequest<T, TArg, TReferences, TArgumentReferences>(
this IDynamoDBMarshaller<T, TArg, TReferences, TArgumentReferences> item,
T entity,
Func<TReferences, TArgumentReferences, string> conditionExpressionBuilder,
ReturnValue returnValue,
string tableName
)
where TReferences : IAttributeExpressionNameTracker
where TArgumentReferences : IAttributeExpressionValueTracker<TArg>
where T : TArg
{
return item.ToPutItemRequestInternal(entity, entity, conditionExpressionBuilder, returnValue, tableName);
}

/// <summary>
/// Creates a <see cref="UpdateItemRequest"/>.
/// </summary>
public static UpdateItemRequest ToUpdateItemRequest<T, TArg, TReferences, TArgumentReferences>(
this IDynamoDBMarshaller<T, TArg, TReferences, TArgumentReferences> item,
TArg argument,
Func<IDynamoDBKeyMarshaller, TArg, Dictionary<string, AttributeValue>> keySelector,
Func<TReferences, TArgumentReferences, string> updateExpressionBuilder,
ReturnValue returnValue,
string tableName
)
where TReferences : IAttributeExpressionNameTracker
where TArgumentReferences : IAttributeExpressionValueTracker<TArg>
{
return item.ToUpdateItemRequestInternal(argument, keySelector, updateExpressionBuilder, null, returnValue, tableName);
}

/// <summary>
/// Creates a <see cref="UpdateItemRequest"/> with a condition expression.
/// </summary>
public static UpdateItemRequest ToUpdateItemRequest<T, TArg, TReferences, TArgumentReferences>(
this IDynamoDBMarshaller<T, TArg, TReferences, TArgumentReferences> item,
TArg argument,
Func<IDynamoDBKeyMarshaller, TArg, Dictionary<string, AttributeValue>> keySelector,
Func<TReferences, TArgumentReferences, string> updateExpressionBuilder,
Func<TReferences, TArgumentReferences, string> conditionExpressionBuilder,
ReturnValue returnValue,
string tableName
)
where TReferences : IAttributeExpressionNameTracker
where TArgumentReferences : IAttributeExpressionValueTracker<TArg>
{
return item.ToUpdateItemRequestInternal(argument, keySelector, updateExpressionBuilder, conditionExpressionBuilder, returnValue, tableName);
}
}
Loading
Loading