Skip to content

Commit

Permalink
Add OfX-Nats.
Browse files Browse the repository at this point in the history
Refactor OfX to make it better!
  • Loading branch information
quyvu01 committed Jan 4, 2025
1 parent 7adf41f commit 2bb8cd2
Show file tree
Hide file tree
Showing 38 changed files with 460 additions and 126 deletions.
3 changes: 2 additions & 1 deletion OfX.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=datas/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=laters/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=laters/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=responser/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ Enjoy your moment!
| [OfX](https://www.nuget.org/packages/OfX/) | OfX core | 8.0, 9.0 | This Document |
| [OfX-EFCore](https://www.nuget.org/packages/OfX-EFCore/) | This is the OfX extension package using EntityFramework to fetch data | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.EntityFrameworkCore/README.md) |
| [OfX-gRPC](https://www.nuget.org/packages/OfX-gRPC/) | OfX.gRPC is an extension package for OfX that leverages gRPC for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.Grpc/README.md) |
| [OfX-Nats](https://www.nuget.org/packages/OfX-Nats/) | OfX-Nats is an extension package for OfX that leverages Nats for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/dev-nats/src/OfX.Nats/README.md) |
2 changes: 1 addition & 1 deletion src/OfX.EntityFrameworkCore/OfX.EntityFrameworkCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<LangVersion>default</LangVersion>
<Version>3.1.1</Version>
<Version>3.1.2</Version>
<Authors>Quy Vu</Authors>
<PackageId>OfX-EFCore</PackageId>
<Description>OfX extension. Use EntityFramework as Data Querying</Description>
Expand Down
68 changes: 38 additions & 30 deletions src/OfX.EntityFrameworkCore/README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,76 @@
# OfX-Nats
# OfX.EntityFrameworkCore

OfX-Nats is an extension package for OfX that leverages Nats for efficient data transportation. This package provides a high-performance, strongly-typed communication layer for OfX’s Attribute-based Data Mapping, enabling streamlined data retrieval across distributed systems.
OfX.EntityFrameworkCore is an extension package for OfX that integrates with Entity Framework Core to simplify data fetching by leveraging attribute-based data mapping. This extension streamlines data retrieval using EF Core, reducing boilerplate code and improving maintainability.

[Demo Project!](https://github.com/quyvu01/TestOfX-Demo)

---

## Introduction

Nats-based Transport: Implements Nats to handle data communication between services, providing a fast, secure, and scalable solution.
OfX.EntityFrameworkCore extends the core OfX library by providing seamless integration with Entity Framework Core. This enables developers to automatically map and retrieve data directly from a database, leveraging the power of EF Core along with attribute-based data mapping.

For example, suppose you have a `UserId` property in your model, and you want to fetch the corresponding `UserName` and `Email` fields from the database. By using OfX.EntityFrameworkCore, you can annotate your model with attributes, and the library will handle data fetching for you.

---

## Installation

To install the OfX-Nats package, use the following NuGet command:
To install the OfX.EntityFrameworkCore package, use the following NuGet command:

```bash
dotnet add package OfX-Nats
dotnet add package OfX-EFCore
```

Or via the NuGet Package Manager:

```bash
Install-Package OfX-Nats
Install-Package OfX-EFCore
```

---

## How to Use

### 1. Register OfX-Nats

Add OfX-Nats to your service configuration during application startup:
### 1. Register OfX.EntityFrameworkCore

For Client:
Add OfX.EntityFrameworkCore to your service configuration during application startup:

```csharp
builder.Services.AddOfXEntityFrameworkCore(cfg =>
{
cfg.AddContractsContainNamespaces(typeof(SomeContractAssemblyMarker).Assembly);
cfg.AddAttributesContainNamespaces(typeof(WhereTheAttributeDefined).Assembly);
cfg.AddHandlersFromNamespaceContaining<SomeHandlerAssemblyMarker>();
cfg.AddNats(config => config
config.UseNats((context, bus) =>
{
bus.Host(host, c =>
{
c.Username(userName);
c.Password(password);
});
bus.ConfigureEndpoints(context);
});
);

})
.AddOfXEFCore(options =>
{
options.AddDbContexts(typeof(TestDbContext));
options.AddModelConfigurationsFromNamespaceContaining<SomeModelAssemblyMarker>();
});
```
That All, enjoy your moment!

After installing the package OfX-EFCore, you can use the method `AddDbContexts()`, which takes `DbContext(s)` to executing.

### 2. Mark the model you want to use with OfXAttribute
Example:

```csharp
[OfXConfigFor<UserOfAttribute>(nameof(Id), nameof(Name))]
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
```
That all! Let go to the moon!

| Package Name | Description | .NET Version | Document |
|----------------------------------------------------------|-------------------------------------------------------------------------------------------------|--------------|------------------------------------------------------------------------------------------|
| [OfX](https://www.nuget.org/packages/OfX/) | OfX core | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/README.md) |
| [OfX-EFCore](https://www.nuget.org/packages/OfX-EFCore/) | This is the OfX extension package using EntityFramework to fetch data | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.EntityFrameworkCore/README.md) |
| [OfX-gRPC](https://www.nuget.org/packages/OfX-gRPC/) | OfX-gRPC is an extension package for OfX that leverages gRPC for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.Grpc/README.md) |
| [OfX-Nats](https://www.nuget.org/packages/OfX-Nats/) | OfX-Nats is an extension package for OfX that leverages Nats for efficient data transportation. | 8.0, 9.0 | This Document |
Note: In this release, Id is exclusively supported as a string. But hold tight—I'm gearing up to blow your mind with the next update! Stay tuned!

| Package Name | Description | .NET Version | Document |
|----------------------------------------------------------|-------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------------------------------------|
| [OfX](https://www.nuget.org/packages/OfX/) | OfX core | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/README.md) |
| [OfX-EFCore](https://www.nuget.org/packages/OfX-EFCore/) | This is the OfX extension package using EntityFramework to fetch data | 8.0, 9.0 | This Document |
| [OfX-gRPC](https://www.nuget.org/packages/OfX-gRPC/) | OfX.gRPC is an extension package for OfX that leverages gRPC for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.Grpc/README.md) |
| [OfX-Nats](https://www.nuget.org/packages/OfX-Nats/) | OfX-Nats is an extension package for OfX that leverages Nats for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/dev-nats/src/OfX.Nats/README.md) |
---
3 changes: 0 additions & 3 deletions src/OfX.Grpc/Exceptions/OfXGrpcExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,4 @@ public sealed class AttributeTypesCannotBeNull()

public sealed class CannotDeserializeOfXAttributeType(string type)
: Exception($"The OfX Attribute seems not a part of this application: {type}!");

public sealed class CannotFindHandlerForOfAttribute(Type type)
: Exception($"Cannot find handler for OfXAttribute type: {type.Name}!");
}
7 changes: 4 additions & 3 deletions src/OfX.Grpc/Extensions/GrpcExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using OfX.Abstractions;
using OfX.Extensions;
using OfX.Grpc.Abstractions;
using OfX.Grpc.ApplicationModels;
using OfX.Grpc.Delegates;
using OfX.Grpc.HandlersInstaller;
using OfX.Grpc.Servers;
using OfX.Helpers;
using OfX.Queries.OfXQueries;
Expand All @@ -19,12 +19,12 @@ namespace OfX.Grpc.Extensions;
public static class GrpcExtensions
{
private static readonly TimeSpan defaultRequestTimeout = TimeSpan.FromSeconds(3);

public static void AddGrpcClients(this OfXRegister ofXRegister, Action<GrpcClientsRegister> options)
{
var newClientsRegister = new GrpcClientsRegister();
options.Invoke(newClientsRegister);
var hostMapAttributes = newClientsRegister.HostMapAttributes;
var attributesRegister = hostMapAttributes.Values.SelectMany(a => a);

ofXRegister.ServiceCollection.TryAddScoped<GetOfXResponseFunc>(_ => attributeType => async (query, context) =>
{
Expand All @@ -39,7 +39,8 @@ public static void AddGrpcClients(this OfXRegister ofXRegister, Action<GrpcClien
]);
return itemsResponse;
});
DefaultGrpcClientsInstaller.InstallerServices(ofXRegister.ServiceCollection, [..attributesRegister]);
Clients.ClientsInstaller.InstallMappableRequestHandlers(ofXRegister.ServiceCollection,
typeof(IOfXGrpcRequestClient<>), [..ofXRegister.OfXAttributeTypes]);
}

private static async Task<OfXItemsGrpcResponse> GetOfXItemsAsync(string serverHost, IContext context,
Expand Down
2 changes: 1 addition & 1 deletion src/OfX.Grpc/OfX.Grpc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<LangVersion>default</LangVersion>
<Version>3.1.1</Version>
<Version>3.1.2</Version>
<Authors>Quy Vu</Authors>
<PackageId>OfX-gRPC</PackageId>
<Description>OfX extension. Use gRPC as Data transporting</Description>
Expand Down
4 changes: 2 additions & 2 deletions src/OfX.Grpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Add OfX-gRPC to your service configuration during application startup:
For Client:

```csharp
builder.Services.AddOfXEntityFrameworkCore(cfg =>
builder.Services.AddOfX(cfg =>
{
cfg.AddContractsContainNamespaces(typeof(SomeContractAssemblyMarker).Assembly);
cfg.AddHandlersFromNamespaceContaining<SomeHandlerAssemblyMarker>();
Expand Down Expand Up @@ -72,5 +72,5 @@ That All, enjoy your moment!
| [OfX](https://www.nuget.org/packages/OfX/) | OfX core | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/README.md) |
| [OfX-EFCore](https://www.nuget.org/packages/OfX-EFCore/) | This is the OfX extension package using EntityFramework to fetch data | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/main/src/OfX.EntityFrameworkCore/README.md) |
| [OfX-gRPC](https://www.nuget.org/packages/OfX-gRPC/) | OfX.gRPC is an extension package for OfX that leverages gRPC for efficient data transportation. | 8.0, 9.0 | This Document |

| [OfX-Nats](https://www.nuget.org/packages/OfX-Nats/) | OfX-Nats is an extension package for OfX that leverages Nats for efficient data transportation. | 8.0, 9.0 | [ReadMe](https://github.com/quyvu01/OfX/blob/dev-nats/src/OfX.Nats/README.md) |
---
17 changes: 4 additions & 13 deletions src/OfX.Grpc/Servers/OfXGrpcServer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;
using Grpc.Core;
using Microsoft.Extensions.DependencyInjection;
using OfX.Abstractions;
using OfX.Cached;
using OfX.Exceptions;
using OfX.Grpc.Exceptions;
using OfX.Implementations;
using OfX.Responses;
Expand All @@ -13,11 +12,6 @@ namespace OfX.Grpc.Servers;

public sealed class OfXGrpcServer(IServiceProvider serviceProvider) : OfXTransportService.OfXTransportServiceBase
{
private const string ExecuteAsync = nameof(ExecuteAsync);

private static readonly Lazy<ConcurrentDictionary<Type, MethodInfo>> MethodInfoStorage =
new(() => new ConcurrentDictionary<Type, MethodInfo>());

public override async Task<OfXItemsGrpcResponse> GetItems(GetOfXGrpcQuery request, ServerCallContext context)
{
try
Expand All @@ -27,18 +21,15 @@ public override async Task<OfXItemsGrpcResponse> GetItems(GetOfXGrpcQuery reques
if (attributeType is null)
throw new OfXGrpcExceptions.CannotDeserializeOfXAttributeType(request.AttributeAssemblyType);

if (!OfXCached.QueryMapHandler.TryGetValue(attributeType, out var handlerType))
throw new OfXGrpcExceptions.CannotFindHandlerForOfAttribute(attributeType);
if (!OfXCached.AttributeMapHandler.TryGetValue(attributeType, out var handlerType))
throw new OfXException.CannotFindHandlerForOfAttribute(attributeType);

var modelArg = handlerType.GetGenericArguments()[0];

var pipeline = serviceProvider
.GetRequiredService(typeof(ReceivedPipelinesImpl<,>).MakeGenericType(modelArg, attributeType));

var pipelineMethod = MethodInfoStorage.Value.GetOrAdd(attributeType, q => pipeline.GetType().GetMethods()
.FirstOrDefault(m =>
m.Name == ExecuteAsync && m.GetParameters() is { Length: 1 } parameters &&
parameters[0].ParameterType == typeof(RequestContext<>).MakeGenericType(q)));
var pipelineMethod = OfXCached.GetPipelineMethodByAttribute(pipeline, attributeType);

var requestContextType = typeof(RequestContextImpl<>).MakeGenericType(attributeType);

Expand Down
10 changes: 10 additions & 0 deletions src/OfX.Nats/Abstractions/INatsRequester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using OfX.Abstractions;
using OfX.Attributes;
using OfX.Responses;

namespace OfX.Nats.Abstractions;

public interface INatsRequester<TAttribute> where TAttribute : OfXAttribute
{
Task<ItemsResponse<OfXDataResponse>> RequestAsync(RequestContext<TAttribute> requestContext);
}
19 changes: 19 additions & 0 deletions src/OfX.Nats/Abstractions/IOfXNatsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using OfX.Abstractions;
using OfX.Attributes;
using OfX.Responses;

namespace OfX.Nats.Abstractions;

public interface IOfXNatsClient<TAttribute> : IMappableRequestHandler<TAttribute> where TAttribute : OfXAttribute
{
IServiceProvider ServiceProvider { get; }

async Task<ItemsResponse<OfXDataResponse>> IMappableRequestHandler<TAttribute>.RequestAsync(
RequestContext<TAttribute> context)
{
var natRequesterService = ServiceProvider.GetRequiredService<INatsRequester<TAttribute>>();
var result = await natRequesterService.RequestAsync(context);
return result;
}
}
13 changes: 13 additions & 0 deletions src/OfX.Nats/ApplicationModels/NatsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace OfX.Nats.ApplicationModels;

public class NatsClient
{
public string NatsHost { get; private set; }
public NatsCredential NatsCredential { get; } = new();

public void Host(string host, Action<NatsCredential> options = null)
{
NatsHost = host;
options?.Invoke(NatsCredential);
}
}
11 changes: 11 additions & 0 deletions src/OfX.Nats/ApplicationModels/NatsClientRegister.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OfX.Nats.ApplicationModels;

public class NatsClientRegister
{
public NatsClient NatsClient { get; } = new();

public void UseNats(Action<NatsClient> options)
{
options.Invoke(NatsClient);
}
}
9 changes: 9 additions & 0 deletions src/OfX.Nats/ApplicationModels/NatsCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace OfX.Nats.ApplicationModels;

public class NatsCredential
{
public string NatsUserName { get; private set; }
public string NatsPassword { get; private set; }
public void UserName(string userName) => NatsUserName = userName;
public void Password(string password) => NatsPassword = password;
}
44 changes: 44 additions & 0 deletions src/OfX.Nats/Extensions/NatsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.Extensions.DependencyInjection;
using NATS.Client;
using OfX.Nats.Abstractions;
using OfX.Nats.ApplicationModels;
using OfX.Nats.Implementations;
using OfX.Nats.Servers;
using OfX.Registries;

namespace OfX.Nats.Extensions;

public static class NatsExtensions
{
public static void AddNats(this OfXRegister ofXRegister, Action<NatsClientRegister> options)
{
var newClientsRegister = new NatsClientRegister();
options.Invoke(newClientsRegister);
// Register NATS connection as a singleton
ofXRegister.ServiceCollection.AddSingleton(_ =>
{
var connectionFactory = new ConnectionFactory();
var natsOptions = ConnectionFactory.GetDefaultOptions();
natsOptions.AllowReconnect = true;
natsOptions.MaxReconnect = -1;
natsOptions.ReconnectWait = 2000;
natsOptions.Timeout = 5000;
natsOptions.Url = newClientsRegister.NatsClient.NatsHost;
natsOptions.User = newClientsRegister.NatsClient.NatsCredential.NatsUserName;
natsOptions.Password = newClientsRegister.NatsClient.NatsCredential.NatsPassword;
return connectionFactory.CreateConnection(natsOptions);
});
ClientsRegister(ofXRegister.ServiceCollection);
Clients.ClientsInstaller.InstallMappableRequestHandlers(ofXRegister.ServiceCollection,
typeof(IOfXNatsClient<>), [..ofXRegister.OfXAttributeTypes]);
}

private static void ClientsRegister(IServiceCollection serviceCollection) =>
serviceCollection.AddScoped(typeof(INatsRequester<>), typeof(NatsRequester<>));

public static void StartNatsServerAsync(this IServiceProvider serviceProvider)
{
var serverListening = new NatsServersListening(serviceProvider);
serverListening.StartAsync();
}
}
27 changes: 27 additions & 0 deletions src/OfX.Nats/Implementations/NatsRequester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text;
using System.Text.Json;
using NATS.Client;
using OfX.Abstractions;
using OfX.Attributes;
using OfX.Nats.Abstractions;
using OfX.Nats.Messages;
using OfX.Responses;

namespace OfX.Nats.Implementations;

public sealed class NatsRequester<TAttribute>(IConnection connection)
: INatsRequester<TAttribute> where TAttribute : OfXAttribute
{
public async Task<ItemsResponse<OfXDataResponse>> RequestAsync(RequestContext<TAttribute> requestContext)
{
var natsMessageWrapped = new NatsMessageRequestWrapped<TAttribute>
{
Query = requestContext.Query,
Headers = requestContext.Headers
};
var reply = await connection.RequestAsync(natsMessageWrapped.Subject, natsMessageWrapped.GetMessageSerialize(),
requestContext.CancellationToken);
var response = Encoding.UTF8.GetString(reply.Data);
return JsonSerializer.Deserialize<ItemsResponse<OfXDataResponse>>(response);
}
}
Loading

0 comments on commit 2bb8cd2

Please sign in to comment.