Compare commits

...

10 Commits

Author SHA1 Message Date
mmdi 6d28773c06 changing database to postgres
CodeQL / Analyze (csharp) (push) Has been cancelled
DevSkim / DevSkim (push) Has been cancelled
Build / build-and-test-macOS-latest (push) Has been cancelled
Build / build-and-test-ubuntu-latest (push) Has been cancelled
Build / build-and-test-windows-latest (push) Has been cancelled
SonarCloud / Build and Analyze (push) Has been cancelled
2025-09-24 19:12:58 +03:30
Jean Gatto d9511f29c7 refactor: add documentation links for SQL Server, MongoDB, and Redis in docker-compose.yml 2025-09-19 16:00:52 -03:00
Jean Francisco Flores Gatto 3c012adcfd Update README.md 2025-09-12 13:56:24 -03:00
Jean Gatto 35f0e303a5 refactor: update environment variables in .env and docker-compose for consistency 2025-09-12 13:31:56 -03:00
Jean Gatto 6c8dfb3170 refactor: update README and docker-compose for clarity and consistency 2025-09-12 07:59:18 -03:00
Jean Gatto 1193852c1b refactor: update SDK version to 9.0.305 in global.json 2025-09-11 16:24:43 -03:00
Jean Gatto db0a2c4227 refactor: update MongoDB package versions to 3.5.* 2025-09-11 16:24:14 -03:00
Jean Gatto 57d89c7b62 refactor: enhance docker-compose configuration for clarity and consistency 2025-09-11 16:22:58 -03:00
Jean Gatto 0ab356ddc6 refactor: revisado e melhora o docker compose 2025-09-11 16:13:13 -03:00
Jean Gatto 5938f9d6a4 refactor: rename variables for clarity and improve health check configuration 2025-09-01 11:03:41 -03:00
34 changed files with 356 additions and 513 deletions
+6
View File
@@ -0,0 +1,6 @@
MSSQL_SA_PASSWORD=Programmer.369
MSSQL_PORT=1433
REDIS_PASSWORD=Programmer.369
REDIS_PORT=6379
MONGO_PORT=27017
ASPNETCORE_ENVIRONMENT=Development
+3 -2
View File
@@ -53,8 +53,9 @@
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.*" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.*" />
<PackageVersion Include="MiniProfiler.EntityFrameworkCore" Version="4.5.*" />
<PackageVersion Include="MongoDB.Bson" Version="3.4.*" />
<PackageVersion Include="MongoDB.Driver" Version="3.4.*" />
<PackageVersion Include="MongoDB.Bson" Version="3.5.*" />
<PackageVersion Include="MongoDB.Driver" Version="3.5.*" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.*" />
<PackageVersion Include="NSubstitute" Version="5.3.*" />
<PackageVersion Include="Polly" Version="8.6.*" />
+23 -9
View File
@@ -1,4 +1,4 @@
# ASP.NET Core C# CQRS Event Sourcing, REST API, DDD, SOLID Principles and Clean Architecture
# ASP.NET Core C# — Clean Architecture, CQRS, Event Sourcing
[![Build](https://github.com/jeangatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing/actions/workflows/dotnet.yml/badge.svg)](https://github.com/jeangatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing/actions/workflows/dotnet.yml)
[![SonarCloud](https://github.com/JeanGatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing/actions/workflows/sonar-cloud.yml/badge.svg)](https://github.com/JeanGatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing/actions/workflows/sonar-cloud.yml)
@@ -16,7 +16,7 @@
[![Sparkline](https://stars.medv.io/jeangatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing.svg)](https://stars.medv.io/jeangatto/ASP.NET-Core-Clean-Architecture-CQRS-Event-Sourcing)
About the repoitory:
About the repository:
Open source project written in the latest version of ASP.NET Core, implementing the concepts of S.O.L.I.D, Clean Code,
CQRS (Command Query Responsibility Segregation)
@@ -32,15 +32,18 @@ If you liked this project, learned something, give it a star. Thank you!
- Entity Framework Core 9
- **EF Compiled Queries** (https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities)
- Unit & Integration Tests + xUnit + FluentAssertions (7.1.0)
- Polly
- AutoMapper (15)
- FluentValidator
- MediatR (13)
- xUnit (https://github.com/xunit/xunit)
- FluentAssertions (https://github.com/fluentassertions/fluentassertions)
- Polly (https://github.com/App-vNext/Polly)
- AutoMapper (https://github.com/LuckyPennySoftware/AutoMapper)
- FluentValidator (https://github.com/FluentValidation/FluentValidation)
- MediatR (https://github.com/LuckyPennySoftware/MediatR)
- Result (https://github.com/ardalis/Result)
- ~~Swagger UI~~
- OpenApi
- **Scalar** - Interactive API Reference from OpenAPI/Swagger (https://github.com/scalar/scalar)
- HealthChecks
- SQL Server
- Microsoft SQL Server
- MongoDB
- Redis (Cache)
- Docker & Docker Compose
@@ -58,7 +61,7 @@ If you liked this project, learned something, give it a star. Thank you!
- Event Sourcing
- Unit of Work
- Repository Pattern
- Resut Pattern
- Result Pattern
## Running the application
@@ -68,10 +71,21 @@ After cloning the repository to the desired folder, run the command in the termi
dotnet clean Shop.sln --nologo /tl && dotnet build Shop.sln --nologo /tl
```
Set passwords in the `.env` file:
```yaml
MSSQL_SA_PASSWORD=YOUR_STRONG_!Passw0rd
REDIS_PASSWORD=YOUR_STRONG_!Passw0rd
MSSQL_PORT=1433
MONGO_PORT=27017
REDIS_PORT=6379
ASPNETCORE_ENVIRONMENT=Development
```
Next step, run the command in the terminal:
```csharp
docker-compose up --build --abort-on-container-exit --remove-orphans
docker-compose up --build
```
Now just open the url in the browser:
+1
View File
@@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
nuget.config = nuget.config
README.md = README.md
requests.http = requests.http
.env = .env
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shop.Query", "src\Shop.Query\Shop.Query.csproj", "{C2A82AAD-0348-4B29-B7E7-E36509EE068C}"
+1 -6
View File
@@ -1,10 +1,5 @@
services:
shop-webapi:
environment:
- ASPNETCORE_ENVIRONMENT=Docker
- ASPNETCORE_URLS=http://+:80
ports:
- "80"
shop-api:
volumes:
- ~/.aspnet/https:/root/.aspnet/https:ro
- ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro
+83 -69
View File
@@ -1,80 +1,94 @@
networks:
app-network:
driver: bridge
services:
# ASP.NET Core Web API Application
shop-webapi:
image: ${DOCKER_REGISTRY-}shop-webapi
container_name: shop-webapi
restart: always
# Microsoft SQL Server
# https://hub.docker.com/r/microsoft/mssql-server/
shop-sql-server:
image: mcr.microsoft.com/mssql/server:2022-latest
restart: unless-stopped
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD}
MSSQL_PID: Express
MSSQL_COLLATION: SQL_Latin1_General_CP1_CI_AS
ports:
- "${MSSQL_PORT}:${MSSQL_PORT}"
volumes:
- sql_data:/var/opt/mssql
networks:
- shop-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# MongoDB
# https://hub.docker.com/_/mongo/
shop-mongo-server:
image: mongo:latest
restart: unless-stopped
ports:
- "${MONGO_PORT}:${MONGO_PORT}"
volumes:
- mongo_data:/data/db
networks:
- shop-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Redis Cache
# https://hub.docker.com/_/redis/
shop-redis-server:
image: redis:latest
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- "${REDIS_PORT}:${REDIS_PORT}"
volumes:
- redis_data:/data
networks:
- shop-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Shop API Container (NET Core)
shop-api:
build:
context: .
dockerfile: src/Shop.PublicApi/Dockerfile
expose:
- 80
links:
- shop-sql-server
- shop-mongo-server
- shop-redis
networks:
- app-network
image: shop-api:latest
restart: on-failure
env_file:
- ./.env
environment:
ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT}
ConnectionStrings__SqlConnection: "Server=shop-sql-server,1433;Database=ShopContext;User Id=sa;Password=${MSSQL_SA_PASSWORD};TrustServerCertificate=True;"
ConnectionStrings__NoSqlConnection: "mongodb://shop-mongo-server:27017"
ConnectionStrings__CacheConnection: "shop-redis-server:6379,password=${REDIS_PASSWORD},abortConnect=false"
ports:
- "5000:80"
depends_on:
- shop-sql-server
- shop-mongo-server
- shop-redis
# SQL Server
shop-sql-server:
image: mcr.microsoft.com/azure-sql-edge:latest
cap_add: ["SYS_PTRACE"]
hostname: mssql
container_name: shop-sql-server
restart: always
expose:
- 1433
ports:
- "1433:1433"
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=${MSSQL_SA_PASSWORD}
- MSSQL_PID=Developer
- MSSQL_COLLATION=Latin1_General_CI_AI
- shop-redis-server
networks:
- app-network
volumes:
- "./data-mssql:/var/opt/mssql/data"
# MongoDB Server
shop-mongo-server:
image: mongo:latest
container_name: shop-mongo-server
restart: always
expose:
- 27017
ports:
- "27017:27017"
networks:
- app-network
# Redis Cache Server
shop-redis:
image: redis:latest
container_name: shop-redis
command: redis-server --requirepass ${REDIS_PASSWORD}
restart: always
expose:
- 6379
ports:
- "6379:6379"
networks:
- app-network
volumes:
- "./data-mongo:/data/db"
- shop-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
data-mssql:
external: true
sql_data:
redis_data:
mongo_data:
data-mongo:
external: true
networks:
shop-network:
driver: bridge
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.304",
"version": "9.0.305",
"rollForward": "latestMajor",
"allowPrerelease": false
}
@@ -8,8 +8,11 @@ public sealed class ConnectionOptions : IAppOptions
{
static string IAppOptions.ConfigSectionPath => "ConnectionStrings";
// [Required]
// public string SqlConnection { get; private init; }
[Required]
public string SqlConnection { get; private init; }
public string PostgresConnection { get; private init; }
[Required]
public string NoSqlConnection { get; private init; }
+1 -27
View File
@@ -1,8 +1,7 @@
using System;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Shop.Core.JsonResolvers;
namespace Shop.Core.Extensions;
@@ -45,28 +44,3 @@ public static class JsonExtensions
return jsonSettings;
}
}
internal sealed class PrivateConstructorContractResolver : DefaultJsonTypeInfoResolver
{
/// <summary>
/// Gets the JSON type information for the specified type, with support for creating objects with private constructors.
/// </summary>
/// <param name="type">The type to get the JSON type information for.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization.</param>
/// <returns>The JSON type information for the specified type.</returns>
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
// Check if the type is an object, has no public constructor, and CreateObject is not already set
if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object
&& jsonTypeInfo.CreateObject is null
&& jsonTypeInfo.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Length == 0)
{
// Set CreateObject to a lambda expression that creates an instance using a private constructor
jsonTypeInfo.CreateObject = () => Activator.CreateInstance(jsonTypeInfo.Type, true);
}
return jsonTypeInfo;
}
}
@@ -0,0 +1,31 @@
using System;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Shop.Core.JsonResolvers;
public sealed class PrivateConstructorContractResolver : DefaultJsonTypeInfoResolver
{
/// <summary>
/// Gets the JSON type information for the specified type, with support for creating objects with private constructors.
/// </summary>
/// <param name="type">The type to get the JSON type information for.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization.</param>
/// <returns>The JSON type information for the specified type.</returns>
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
var jsonTypeInfo = base.GetTypeInfo(type, options);
// Check if the type is an object, has no public constructor, and CreateObject is not already set
if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object
&& jsonTypeInfo.CreateObject is null
&& jsonTypeInfo.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance).Length == 0)
{
// Set CreateObject to a lambda expression that creates an instance using a private constructor
jsonTypeInfo.CreateObject = () => Activator.CreateInstance(jsonTypeInfo.Type, true);
}
return jsonTypeInfo;
}
}
@@ -7,7 +7,7 @@ namespace Shop.Infrastructure.Data.Context;
public abstract class BaseDbContext<TContext>(DbContextOptions<TContext> dbOptions) : DbContext(dbOptions)
where TContext : DbContext
{
private const string Collation = "Latin1_General_CI_AI";
//private const string Collation = "en_US.UTF-8";
public override ChangeTracker ChangeTracker
{
@@ -33,7 +33,7 @@ public abstract class BaseDbContext<TContext>(DbContextOptions<TContext> dbOptio
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseCollation(Collation)
// .UseCollation(Collation)
.RemoveCascadeDeleteConvention();
base.OnModelCreating(modelBuilder);
@@ -26,5 +26,8 @@ internal static class EntityTypeBuilderExtensions
// Ignores the DomainEvents property for the entity.
builder
.Ignore(entity => entity.DomainEvents);
}
}
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Shop.Infrastructure.Data.Extensions;
@@ -21,5 +23,27 @@ internal static class ModelBuilderExtensions
// Change the delete behavior of each foreign key to restrict
foreach (var fk in foreignKeys)
fk.DeleteBehavior = DeleteBehavior.Restrict;
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime))
{
property.SetValueConverter(new ValueConverter<DateTime, DateTime>(
v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc)
));
}
else if (property.ClrType == typeof(DateTime?))
{
property.SetValueConverter(new ValueConverter<DateTime?, DateTime?>(
v => v.HasValue ? (v.Value.Kind == DateTimeKind.Utc ? v : v.Value.ToUniversalTime()) : v,
v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v
));
}
}
}
}
}
+8 -3
View File
@@ -33,10 +33,13 @@ internal sealed class UnitOfWork(
logger.LogInformation("----- Begin transaction: '{TransactionId}'", transaction.TransactionId);
IReadOnlyList<BaseEvent> domainEvents = Array.Empty<BaseEvent>();
IReadOnlyList<EventStore> eventStores = Array.Empty<EventStore>();
try
{
// Getting the domain events and event stores from the tracked entities in the EF Core context.
var (domainEvents, eventStores) = BeforeSaveChanges();
(domainEvents, eventStores) = BeforeSaveChanges();
var rowsAffected = await writeDbContext.SaveChangesAsync();
@@ -44,8 +47,7 @@ internal sealed class UnitOfWork(
await transaction.CommitAsync();
// Triggering the events and saving the stores.
await AfterSaveChangesAsync(domainEvents, eventStores);
logger.LogInformation(
"----- Transaction successfully confirmed: '{TransactionId}', Rows Affected: {RowsAffected}",
@@ -64,6 +66,9 @@ internal sealed class UnitOfWork(
throw;
}
// Triggering the events and saving the stores.
await AfterSaveChangesAsync(domainEvents, eventStores);
});
}
+6 -3
View File
@@ -1,7 +1,7 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 80
ENV ASPNETCORE_URLS=http://+:80
# Specifies whether .NET welcome and telemetry messages are displayed on the first run.
@@ -10,6 +10,9 @@ ENV DOTNET_NOLOGO=1
# Specifies whether data about the .NET tools usage is collected and sent to Microsoft.
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Defines the build configuration to use at build time. The default is 'Release'
ARG BUILD_CONFIGURATION=Release
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS build
WORKDIR /src
COPY ["Directory.Packages.props", "."]
@@ -24,10 +27,10 @@ COPY ["src/Shop.Query/Shop.Query.csproj", "src/Shop.Query/"]
RUN dotnet restore "src/Shop.PublicApi/Shop.PublicApi.csproj"
COPY . .
WORKDIR "/src/src/Shop.PublicApi"
RUN dotnet build "Shop.PublicApi.csproj" --configuration Release --no-restore --nologo --output /app/build
RUN dotnet build "Shop.PublicApi.csproj" --configuration $BUILD_CONFIGURATION --no-restore --nologo --output /app/build
FROM build AS publish
RUN dotnet publish "Shop.PublicApi.csproj" --configuration Release --no-restore --nologo --output /app/publish
RUN dotnet publish "Shop.PublicApi.csproj" --configuration $BUILD_CONFIGURATION --no-restore --nologo --output /app/publish
FROM base AS final
WORKDIR /app
@@ -29,16 +29,16 @@ internal static class ServicesCollectionExtensions
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
var options = configuration.GetOptions<ConnectionOptions>();
var connectionOptions = configuration.GetOptions<ConnectionOptions>();
var healthCheckBuilder = services
.AddHealthChecks()
.AddDbContextCheck<WriteDbContext>(tags: DbRelationalTags)
.AddDbContextCheck<EventStoreDbContext>(tags: DbRelationalTags)
.AddMongoDb(clientFactory: _ => new MongoClient(options.NoSqlConnection), tags: DbNoSqlTags);
.AddMongoDb(clientFactory: _ => new MongoClient(connectionOptions.NoSqlConnection), tags: DbNoSqlTags);
if (!options.CacheConnectionInMemory())
healthCheckBuilder.AddRedis(options.CacheConnection);
if (!connectionOptions.CacheConnectionInMemory())
healthCheckBuilder.AddRedis(connectionOptions.CacheConnection);
return services;
}
@@ -48,12 +48,10 @@ internal static class ServicesCollectionExtensions
if (!environment.IsEnvironment(TestingEnvironmentName))
{
services.AddDbContextPool<WriteDbContext>((serviceProvider, optionsBuilder) =>
ConfigureDbContext<WriteDbContext>(
serviceProvider, optionsBuilder, QueryTrackingBehavior.TrackAll));
ConfigureDbContext<WriteDbContext>(serviceProvider, optionsBuilder, QueryTrackingBehavior.TrackAll));
services.AddDbContextPool<EventStoreDbContext>((serviceProvider, optionsBuilder) =>
ConfigureDbContext<EventStoreDbContext>(
serviceProvider, optionsBuilder, QueryTrackingBehavior.NoTrackingWithIdentityResolution));
ConfigureDbContext<EventStoreDbContext>(serviceProvider, optionsBuilder, QueryTrackingBehavior.NoTrackingWithIdentityResolution));
}
return services;
@@ -85,13 +83,13 @@ internal static class ServicesCollectionExtensions
DbContextOptionsBuilder optionsBuilder,
QueryTrackingBehavior queryTrackingBehavior) where TDbContext : DbContext
{
var connectionOptions = serviceProvider.GetOptions<ConnectionOptions>();
var logger = serviceProvider.GetRequiredService<ILogger<TDbContext>>();
var options = serviceProvider.GetOptions<ConnectionOptions>();
var environment = serviceProvider.GetRequiredService<IHostEnvironment>();
var envIsDevelopment = environment.IsDevelopment();
optionsBuilder
.UseSqlServer(options.SqlConnection, sqlServerOptions =>
.UseNpgsql(connectionOptions.PostgresConnection, sqlServerOptions =>
{
sqlServerOptions
.MigrationsAssembly(DbMigrationAssemblyName)
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
@@ -12,7 +13,7 @@ namespace Shop.PublicApi.Extensions;
internal static class WebApplicationExtensions
{
public static async Task RunAppAsync(this WebApplication app)
public static async Task ConfigureAndRunAppAsync(this WebApplication app)
{
await using var serviceScope = app.Services.CreateAsyncScope();
@@ -62,8 +63,20 @@ internal static class WebApplicationExtensions
app.Logger.LogInformation("----- {DbName}: checking if there are any pending migrations...", dbName);
var allMigrations = dbContext.Database.GetMigrations();
app.Logger.LogInformation("----- {DbName}: {MigrationsCount} migrations were found", dbName, allMigrations.Count());
var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync();
app.Logger.LogInformation("----- {DbName}: {AppliedMigrationsCount} applied migrations were found", dbName, appliedMigrations.Count());
var pendingMigrations = allMigrations.Except(appliedMigrations).ToList();
app.Logger.LogInformation("----- {DbName}: {PendingMigrationsCount} pending migrations were found", dbName, pendingMigrations.Count);
// Check if there are any pending migrations for the context.
if (dbContext.Database.HasPendingModelChanges())
if (pendingMigrations.Count > 0)
{
app.Logger.LogInformation("----- {DbName}: creating and migrating the database...", dbName);
@@ -1,89 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Shop.Infrastructure.Data.Context;
#nullable disable
namespace Shop.PublicApi.Migrations
{
[DbContext(typeof(WriteDbContext))]
[Migration("20230207164309_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Shop.Domain.Entities.Customer.Customer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("DATE");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
b.Property<string>("Gender")
.IsRequired()
.HasMaxLength(6)
.IsUnicode(false)
.HasColumnType("varchar(6)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
b.HasKey("Id");
b.ToTable("Customers");
});
modelBuilder.Entity("Shop.Domain.Entities.Customer.Customer", b =>
{
b.OwnsOne("Shop.Domain.ValueObjects.Email", "Email", b1 =>
{
b1.Property<Guid>("CustomerId")
.HasColumnType("uniqueidentifier");
b1.Property<string>("Address")
.IsRequired()
.HasMaxLength(254)
.IsUnicode(false)
.HasColumnType("varchar(254)")
.HasColumnName("Email");
b1.HasKey("CustomerId");
b1.HasIndex("Address")
.IsUnique()
.HasFilter("[Email] IS NOT NULL");
b1.ToTable("Customers");
b1.WithOwner()
.HasForeignKey("CustomerId");
});
b.Navigation("Email");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,39 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Shop.PublicApi.Migrations;
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Customers",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FirstName = table.Column<string>(type: "varchar(100)", unicode: false, maxLength: 100, nullable: false),
LastName = table.Column<string>(type: "varchar(100)", unicode: false, maxLength: 100, nullable: false),
Gender = table.Column<string>(type: "varchar(6)", unicode: false, maxLength: 6, nullable: false),
Email = table.Column<string>(type: "varchar(254)", unicode: false, maxLength: 254, nullable: true),
DateOfBirth = table.Column<DateTime>(type: "DATE", nullable: false)
},
constraints: table => table.PrimaryKey("PK_Customers", x => x.Id));
migrationBuilder.CreateIndex(
name: "IX_Customers_Email",
table: "Customers",
column: "Email",
unique: true,
filter: "[Email] IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Customers");
}
}
@@ -1,39 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Shop.PublicApi.Migrations;
/// <inheritdoc />
public partial class EF_Update : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Gender",
table: "Customers",
type: "nvarchar(6)",
maxLength: 6,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(6)",
oldUnicode: false,
oldMaxLength: 6);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Gender",
table: "Customers",
type: "varchar(6)",
unicode: false,
maxLength: 6,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(6)",
oldMaxLength: 6);
}
}
@@ -2,9 +2,9 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Shop.Infrastructure.Data.Context;
#nullable disable
@@ -12,24 +12,23 @@ using Shop.Infrastructure.Data.Context;
namespace Shop.PublicApi.Migrations
{
[DbContext(typeof(WriteDbContext))]
[Migration("20241128202054_EF_Update")]
partial class EF_Update
[Migration("20250924110748_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Shop.Domain.Entities.CustomerAggregate.Customer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("DATE");
@@ -38,18 +37,18 @@ namespace Shop.PublicApi.Migrations
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.Property<string>("Gender")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("nvarchar(6)");
.HasColumnType("character varying(6)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.HasKey("Id");
@@ -61,20 +60,19 @@ namespace Shop.PublicApi.Migrations
b.OwnsOne("Shop.Domain.ValueObjects.Email", "Email", b1 =>
{
b1.Property<Guid>("CustomerId")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b1.Property<string>("Address")
.IsRequired()
.HasMaxLength(254)
.IsUnicode(false)
.HasColumnType("varchar(254)")
.HasColumnType("character varying(254)")
.HasColumnName("Email");
b1.HasKey("CustomerId");
b1.HasIndex("Address")
.IsUnique()
.HasFilter("[Email] IS NOT NULL");
.IsUnique();
b1.ToTable("Customers");
@@ -0,0 +1,44 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Shop.PublicApi.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Customers",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
FirstName = table.Column<string>(type: "character varying(100)", unicode: false, maxLength: 100, nullable: false),
LastName = table.Column<string>(type: "character varying(100)", unicode: false, maxLength: 100, nullable: false),
Gender = table.Column<string>(type: "character varying(6)", maxLength: 6, nullable: false),
Email = table.Column<string>(type: "character varying(254)", unicode: false, maxLength: 254, nullable: true),
DateOfBirth = table.Column<DateTime>(type: "DATE", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Customers", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Customers_Email",
table: "Customers",
column: "Email",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Customers");
}
}
}
@@ -1,58 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Shop.Infrastructure.Data.Context;
#nullable disable
namespace Shop.PublicApi.Migrations.EventStoreDb
{
[DbContext(typeof(EventStoreDbContext))]
[Migration("20230226145437_EventStore")]
partial class EventStore
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Shop.Core.Events.EventStore", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Data")
.IsRequired()
.IsUnicode(false)
.HasColumnType("VARCHAR(MAX)")
.HasComment("JSON serialized event");
b.Property<string>("MessageType")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
b.Property<DateTime>("OccurredOn")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.HasKey("Id");
b.ToTable("EventStores");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,31 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Shop.PublicApi.Migrations.EventStoreDb;
/// <inheritdoc />
public partial class EventStore : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EventStores",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Data = table.Column<string>(type: "VARCHAR(MAX)", unicode: false, nullable: false, comment: "JSON serialized event"),
MessageType = table.Column<string>(type: "varchar(100)", unicode: false, maxLength: 100, nullable: false),
AggregateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table => table.PrimaryKey("PK_EventStores", x => x.Id));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EventStores");
}
}
@@ -1,43 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Shop.PublicApi.Migrations.EventStoreDb;
/// <inheritdoc />
public partial class EF_Update : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Data",
table: "EventStores",
type: "varchar(255)",
unicode: false,
maxLength: 255,
nullable: false,
oldClrType: typeof(string),
oldType: "VARCHAR(MAX)",
oldUnicode: false,
comment: "JSON serialized event",
oldComment: "JSON serialized event");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Data",
table: "EventStores",
type: "VARCHAR(MAX)",
unicode: false,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255)",
oldUnicode: false,
oldMaxLength: 255,
comment: "JSON serialized event",
oldComment: "JSON serialized event");
}
}
@@ -2,53 +2,52 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Shop.Infrastructure.Data.Context;
#nullable disable
namespace Shop.PublicApi.Migrations.EventStoreDb
namespace Shop.PublicApi.Migrations.EventStoreMigrations
{
[DbContext(typeof(EventStoreDbContext))]
[Migration("20241128202230_EF_Update")]
partial class EF_Update
[Migration("20250924110826_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Shop.Core.SharedKernel.EventStore", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(255)
.IsUnicode(false)
.HasColumnType("varchar(255)")
.HasColumnType("character varying(255)")
.HasComment("JSON serialized event");
b.Property<string>("MessageType")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.Property<DateTime>("OccurredOn")
.HasColumnType("datetime2")
.HasColumnType("timestamp with time zone")
.HasColumnName("CreatedAt");
b.HasKey("Id");
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Shop.PublicApi.Migrations.EventStoreMigrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EventStores",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Data = table.Column<string>(type: "character varying(255)", unicode: false, maxLength: 255, nullable: false, comment: "JSON serialized event"),
MessageType = table.Column<string>(type: "character varying(100)", unicode: false, maxLength: 100, nullable: false),
AggregateId = table.Column<Guid>(type: "uuid", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EventStores", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EventStores");
}
}
}
@@ -2,13 +2,13 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Shop.Infrastructure.Data.Context;
#nullable disable
namespace Shop.PublicApi.Migrations.EventStoreDb
namespace Shop.PublicApi.Migrations.EventStoreMigrations
{
[DbContext(typeof(EventStoreDbContext))]
partial class EventStoreDbContextModelSnapshot : ModelSnapshot
@@ -17,35 +17,34 @@ namespace Shop.PublicApi.Migrations.EventStoreDb
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Shop.Core.SharedKernel.EventStore", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(255)
.IsUnicode(false)
.HasColumnType("varchar(255)")
.HasColumnType("character varying(255)")
.HasComment("JSON serialized event");
b.Property<string>("MessageType")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.Property<DateTime>("OccurredOn")
.HasColumnType("datetime2")
.HasColumnType("timestamp with time zone")
.HasColumnName("CreatedAt");
b.HasKey("Id");
@@ -2,8 +2,8 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Shop.Infrastructure.Data.Context;
#nullable disable
@@ -17,16 +17,15 @@ namespace Shop.PublicApi.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.UseCollation("Latin1_General_CI_AI")
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Shop.Domain.Entities.CustomerAggregate.Customer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("DATE");
@@ -35,18 +34,18 @@ namespace Shop.PublicApi.Migrations
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.Property<string>("Gender")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("nvarchar(6)");
.HasColumnType("character varying(6)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false)
.HasColumnType("varchar(100)");
.HasColumnType("character varying(100)");
b.HasKey("Id");
@@ -58,20 +57,19 @@ namespace Shop.PublicApi.Migrations
b.OwnsOne("Shop.Domain.ValueObjects.Email", "Email", b1 =>
{
b1.Property<Guid>("CustomerId")
.HasColumnType("uniqueidentifier");
.HasColumnType("uuid");
b1.Property<string>("Address")
.IsRequired()
.HasMaxLength(254)
.IsUnicode(false)
.HasColumnType("varchar(254)")
.HasColumnType("character varying(254)")
.HasColumnName("Email");
b1.HasKey("CustomerId");
b1.HasIndex("Address")
.IsUnique()
.HasFilter("[Email] IS NOT NULL");
.IsUnique();
b1.ToTable("Customers");
+2 -1
View File
@@ -1,3 +1,4 @@
using System;
using System.Globalization;
using System.IO.Compression;
using Asp.Versioning;
@@ -139,4 +140,4 @@ app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await app.RunAppAsync();
await app.ConfigureAndRunAppAsync();
+1
View File
@@ -40,6 +40,7 @@
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" />
<PackageReference Include="MiniProfiler.EntityFrameworkCore" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="Roslynator.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -9,11 +9,6 @@
"Microsoft.AspNetCore.DataProtection": "Information"
}
},
"ConnectionStrings": {
"SqlConnection": "",
"NoSqlConnection": "",
"CacheConnection": ""
},
"CacheOptions": {
"AbsoluteExpirationInHours": 2,
"SlidingExpirationInSeconds": 60
@@ -1,21 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Information",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Information",
"Microsoft.AspNetCore.DataProtection": "Information"
}
},
"ConnectionStrings": {
"SqlConnection": "",
"NoSqlConnection": "",
"CacheConnection": ""
},
"CacheOptions": {
"AbsoluteExpirationInHours": 2,
"SlidingExpirationInSeconds": 60
}
}
+6
View File
@@ -6,5 +6,11 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"PostgresConnection" : "Host=localhost;Port=5432;Database=ShopDDD;Username=mmdi;Password=mmdi",
"SqlConnection": "Server=localhost,1433;Database=ShopContext;User Id=sa;Password=Programmer.369;TrustServerCertificate=True;",
"NoSqlConnection" : "mongodb://localhost:27017",
"CacheConnection" : "localhost:6379,password=Programmer.369,abortConnect=false"
},
"AllowedHosts": "*"
}