Skip to content

Commit

Permalink
x
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterKneale committed Dec 30, 2022
1 parent e842c11 commit 26108c9
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 34 deletions.
7 changes: 7 additions & 0 deletions Demo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Modules.Tenants.Int
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "backend", "backend", "{7EB35F3D-8510-4899-8A61-C4D2362DA822}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Modules.Settings.IntegrationTests", "tests\Backend.Modules.Settings.IntegrationTests\Backend.Modules.Settings.IntegrationTests.csproj", "{CD307B35-5A4F-4B94-BC25-2A5961B860D0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -118,6 +120,10 @@ Global
{983CF288-DDD8-43F6-ADBD-88961D89F72D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{983CF288-DDD8-43F6-ADBD-88961D89F72D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{983CF288-DDD8-43F6-ADBD-88961D89F72D}.Release|Any CPU.Build.0 = Release|Any CPU
{CD307B35-5A4F-4B94-BC25-2A5961B860D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD307B35-5A4F-4B94-BC25-2A5961B860D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD307B35-5A4F-4B94-BC25-2A5961B860D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD307B35-5A4F-4B94-BC25-2A5961B860D0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A10CE326-0725-459B-864D-83BBD8E13034} = {73B2D212-C184-4F69-B7D2-296834C288D1}
Expand All @@ -137,5 +143,6 @@ Global
{983CF288-DDD8-43F6-ADBD-88961D89F72D} = {7EB35F3D-8510-4899-8A61-C4D2362DA822}
{AE4A616F-EE8F-4112-8D46-7B89DA71684D} = {7EB35F3D-8510-4899-8A61-C4D2362DA822}
{5686EB1A-2C59-4512-B8F2-EB5E9A30E0F8} = {7EB35F3D-8510-4899-8A61-C4D2362DA822}
{CD307B35-5A4F-4B94-BC25-2A5961B860D0} = {7EB35F3D-8510-4899-8A61-C4D2362DA822}
EndGlobalSection
EndGlobal
26 changes: 18 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@

- todo

# Backend
# Backend - Modular Monolith

### Admin API
### Tenants API
- Executes use cases in the context of an administrator on the platform
- The security policy defined below allows read-only access to all tenant data

### Tenant API
### Settings API
- Executes use cases in the context of a specific tenant on the platform
- The security policy defined below allows full access to the specified tenants data

### Statistics API
- Executes use cases in the context of a specific tenant on the platform
- The security policy defined below allows full access to the specified tenants data

### Widgets API
- Executes use cases in the context of a specific tenant on the platform
- The security policy defined below allows full access to the specified tenants data

Expand Down Expand Up @@ -42,7 +50,7 @@
## Database schema
Create a table for use by multiple tenants
```cs
Create.Table("cars")
Create.Table("widgets")
.WithColumn("id").AsGuid().NotNullable().PrimaryKey()
.WithColumn("tenant").AsString().NotNullable() // This column indicates which tenant a row belongs to
.WithColumn("registration").AsString().Nullable().Unique()
Expand Down Expand Up @@ -84,11 +92,13 @@ Execute.Sql($"CREATE POLICY {Policy} ON {Table} FOR ALL TO {Username} USING ({Co

## Build and Deploy
```shell
docker build -f Frontend/Dockerfile . -t peterkneale/frontend
docker build -f Admin/Dockerfile . -t peterkneale/admin
docker build -f Registration/Dockerfile . -t peterkneale/registration
docker build -f src/Admin/Dockerfile . -t peterkneale/admin
docker build -f src/Backend/Dockerfile . -t peterkneale/backend
docker build -f src/Frontend/Dockerfile . -t peterkneale/frontend
docker build -f src/Registration/Dockerfile . -t peterkneale/registration

docker push peterkneale/frontend
docker push peterkneale/admin
docker push peterkneale/backend
docker push peterkneale/frontend
docker push peterkneale/registration
```
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ public void ResetTheme()

public string? Theme { get; private set; }

public string GetTheme() => Theme ?? "Default";
public string GetTheme() => Theme ?? DefaultThemeName;

public static Settings Create() =>
new();

public const string DefaultThemeName = "Default";
}
18 changes: 5 additions & 13 deletions src/Backend.Modules.Settings/Infrastructure/SettingsRepository.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
using Backend.Modules.Infrastructure.Repositories.Serialisation;
using Backend.Modules.Infrastructure.Tenancy;

namespace Backend.Modules.Settings.Infrastructure;
using static Backend.Modules.Infrastructure.Database.Constants;

internal class SettingsRepository : ISettingsRepository
{
private readonly IGetTenantContext _context;
private readonly IDbConnection _connection;

public SettingsRepository(ITenantConnectionFactory factory, IGetTenantContext context)
public SettingsRepository(ITenantConnectionFactory factory)
{
_context = context;
_connection = factory.GetDbConnectionForTenant();
}

public async Task Insert(Domain.SettingsAggregate.Settings settings, CancellationToken cancellationToken)
{
const string sql = $"insert into {TableSettings} ({ColumnTenantId}, {ColumnData}) values (@id, @data::jsonb)";
const string sql = $"insert into {TableSettings} ({ColumnData}) values (@data::jsonb)";
var json = JsonHelper.ToJson(settings);
await _connection.ExecuteAsync(sql, new
{
id = _context.CurrentTenant,
data = json
});
}

public async Task Update(Domain.SettingsAggregate.Settings settings, CancellationToken cancellationToken)
{
const string sql = $"update {TableSettings} set {ColumnData} = @data::jsonb where {ColumnTenantId} = @id";
const string sql = $"update {TableSettings} set {ColumnData} = @data::jsonb";
var result = await _connection.ExecuteAsync(sql, new
{
id = _context.CurrentTenant,
data = JsonHelper.ToJson(settings)
});
if (result != 1)
Expand All @@ -42,11 +37,8 @@ public async Task Update(Domain.SettingsAggregate.Settings settings, Cancellatio

public async Task<Domain.SettingsAggregate.Settings?> Get(CancellationToken cancellationToken)
{
const string sql = $"select {ColumnData} from {TableSettings} where {ColumnTenantId} = @id";
var result = await _connection.QuerySingleOrDefaultAsync<string>(sql, new
{
id = _context.CurrentTenant
});
const string sql = $"select {ColumnData} from {TableSettings}";
var result = await _connection.QuerySingleOrDefaultAsync<string>(sql);
return JsonHelper.ToObject<Domain.SettingsAggregate.Settings>(result);
}
}
6 changes: 2 additions & 4 deletions src/Backend.Modules.Tenants/Api/TenantsApi.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ package Backend;

option csharp_namespace = "Backend.Api";

service TenantsApi {

service TenantsApi {
// commands
rpc AddTenant(AddTenantRequest) returns (EmptyResponse2);
rpc Claim(ClaimRequest) returns (EmptyResponse2);
Expand All @@ -14,8 +13,7 @@ service TenantsApi {
// queries
rpc GetTenant(GetTenantRequest) returns (GetTenantResponse);
rpc GetTenantByIdentifier(GetTenantByIdentifierRequest) returns (GetTenantByIdentifierResponse);
rpc ListTenants(ListTenantsRequest) returns (ListTenantsResponse);

rpc ListTenants(ListTenantsRequest) returns (ListTenantsResponse);
}

// commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ namespace Backend.Modules.Widgets.Infrastructure;
internal class WidgetRepository : IWidgetRepository
{
private readonly IDbConnection _connection;
private readonly IGetTenantContext _context;

public WidgetRepository(ITenantConnectionFactory factory, IGetTenantContext context)
public WidgetRepository(ITenantConnectionFactory factory)
{
_connection = factory.GetDbConnectionForTenant();
_context = context;
}

public async Task<Widget?> Get(WidgetId widgetId, CancellationToken cancellationToken)
Expand All @@ -36,12 +34,11 @@ public async Task<IEnumerable<Widget>> List(CancellationToken cancellationToken)

public async Task Insert(Widget widget, CancellationToken cancellationToken)
{
const string sql = $"insert into {TableWidgets} ({ColumnId}, {ColumnTenantId}, {ColumnData}) values (@id, @tenant_id, @data::jsonb)";
const string sql = $"insert into {TableWidgets} ({ColumnId}, {ColumnData}) values (@id, @data::jsonb)";
var json = JsonHelper.ToJson(widget);
await _connection.ExecuteAsync(sql, new
{
id = widget.Id.Id,
tenant_id = _context.CurrentTenant,
data = json
});
}
Expand All @@ -59,5 +56,4 @@ public async Task Update(Widget widget, CancellationToken cancellationToken)
throw new Exception("Record not updated");
}
}

}
4 changes: 2 additions & 2 deletions src/Backend.Modules/Infrastructure/Database/Migration1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ public override void Up()
.WithColumn(Constants.ColumnData).AsCustom("jsonb").NotNullable();

Create.Table(Constants.TableSettings)
.WithColumn(Constants.ColumnTenantId).AsGuid().NotNullable().PrimaryKey()
.WithColumn(Constants.ColumnTenantId).AsGuid().NotNullable().PrimaryKey().WithDefaultValue(RawSql.Insert("current_setting('app.tenant_id')::uuid"))
.WithColumn(Constants.ColumnData).AsCustom("jsonb").NotNullable();

Create.Table(Constants.TableWidgets)
.WithColumn(Constants.ColumnId).AsGuid().NotNullable().PrimaryKey()
.WithColumn(Constants.ColumnTenantId).AsGuid().NotNullable()
.WithColumn(Constants.ColumnTenantId).AsGuid().NotNullable().WithDefaultValue(RawSql.Insert("current_setting('app.tenant_id')::uuid"))
.WithColumn(Constants.ColumnData).AsCustom("jsonb").NotNullable();

// This table should have row level security that ensure a tenant can only manage their own data
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Backend.Modules.Settings\Backend.Modules.Settings.csproj" />
<ProjectReference Include="..\..\src\Backend.Modules\Backend.Modules.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Backend.Modules.Settings.IntegrationTests.Fixtures;

[CollectionDefinition(nameof(ContainerCollectionFixture))]
public class ContainerCollectionFixture : ICollectionFixture<ContainerFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Backend.Modules.Infrastructure.Database;

namespace Backend.Modules.Settings.IntegrationTests.Fixtures;

public class ContainerFixture : IDisposable
{
private readonly ServiceProvider _provider;

public ContainerFixture()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection()
.AddEnvironmentVariables()
.Build();

var services = new ServiceCollection()
.AddModules(configuration)
.AddSettings(configuration);

services
.AddSingleton<IConfiguration>(configuration);

_provider = services.BuildServiceProvider();
_provider.ExecuteDatabaseMigration(x => x.ResetDatabase());
}

public IServiceProvider Provider => _provider;

public void Dispose()
{
_provider.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Backend.Modules.Infrastructure.Tenancy;

namespace Backend.Modules.Settings.IntegrationTests.Fixtures;

public static class ProviderExtensions
{
public static async Task ExecuteCommand(this IServiceProvider provider, IRequest command, Guid? tenant = null)
{
using var scope = provider.CreateScope();
if (tenant != null)
{
SetTenantContext(tenant.Value, scope);
}
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Send(command);
}

public static async Task<T> ExecuteQuery<T>(this IServiceProvider provider, IRequest<T> query, Guid? tenant = null)
{
using var scope = provider.CreateScope();
if (tenant != null)
{
SetTenantContext(tenant.Value, scope);
}
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
return await mediator.Send(query);
}

private static void SetTenantContext(Guid tenant, IServiceScope scope)
{
// resolve the tenant context so that it can be set for this use case
var setter = scope.ServiceProvider.GetRequiredService<ISetTenantContext>();
setter.SetCurrentTenant(tenant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Global using directives
global using System;
global using System.Collections.Generic;
global using System.Threading.Tasks;
global using FluentAssertions;
global using MediatR;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Xunit;
Loading

0 comments on commit 26108c9

Please sign in to comment.