بلاخره تموم شد

This commit is contained in:
2025-09-01 21:02:10 +04:30
parent 81e43f5b92
commit 746b6159e4
55 changed files with 706 additions and 122 deletions
@@ -0,0 +1,12 @@
using System.Data;
namespace VizhTalk.BuildingBlocks.Application.Data;
public interface ISqlConnectionFactory
{
IDbConnection GetOpenConnection();
IDbConnection CreateNewConnection();
string GetConnectionString();
}
@@ -1,7 +1,7 @@
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Infrastructure;
namespace VizhTalk.BuildingBlocks.Application;
public interface IExecutor
{
@@ -0,0 +1,7 @@
namespace VizhTalk.BuildingBlocks.Application;
public interface IUnitOfWorkBase : IDisposable
{
Task<int> CommitAsync(
CancellationToken cancellationToken = default);
}
@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Domain\VizhTalk.BuildingBlocks.Domain.csproj" />
<PackageReference Include="Mapster" Version="7.4.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Domain\VizhTalk.BuildingBlocks.Domain.csproj" />
</ItemGroup>
</Project>
@@ -1,27 +0,0 @@
namespace VizhTalk.BuildingBlocks.Domain.Contract;
public abstract record CommandBase<T> : ICommand<T>
{
public Guid Id { get; private set; }
protected CommandBase()
{
Id = Guid.NewGuid();
}
protected CommandBase(Guid id)
{
Id = id;
}
}
public abstract record CommandBase : ICommand
{
public Guid Id { get; private set; }
protected CommandBase()
{
Id = Guid.NewGuid();
}
protected CommandBase(Guid id)
{
Id = id;
}
}
@@ -2,10 +2,8 @@
public interface ICommand<out TResult>
{
Guid Id { get; }
}
public interface ICommand
{
Guid Id { get; }
}
+5
View File
@@ -37,4 +37,9 @@ public abstract class Entity<T>
}
}
public void SetDeleted(bool val)
=> IsDeleted = val;
public void SetActive(bool val)
=> IsActive = val;
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Domain.Exceptions.GuardExceptions
{
public class MaxLengthException(string name, int length)
: Exception($"The {name} field cannot be longer than {length} characters."), IGuardException
{
}
}
@@ -7,6 +7,6 @@ using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Domain.Exceptions.GuardExceptions;
public class NullOrEmptyException(string message) : Exception(message), IGuardException
public class NullOrEmptyException(string name) : Exception($"{name} cannot be empty"), IGuardException
{
}
@@ -4,5 +4,8 @@
{
public static void NullOrEmpty(this string value, string name)
=> StringGuard.NullOrEmpty(value, name);
public static void MaxLength(this string value, string name, int length)
=> StringGuard.MaxLength(value, length, name);
}
}
@@ -4,9 +4,15 @@ namespace VizhTalk.BuildingBlocks.Domain.Guards;
internal static class StringGuard
{
internal static void NullOrEmpty(string value,string name)
internal static void NullOrEmpty(string value, string name)
{
if(string.IsNullOrEmpty(value))
throw new NullOrEmptyException($"{name} cannot be empty");
if (string.IsNullOrEmpty(value))
throw new NullOrEmptyException(name);
}
internal static void MaxLength(string value, int length, string name)
{
if (value.Length > length)
throw new MaxLengthException(name, length);
}
}
@@ -1,10 +1,12 @@
namespace VizhTalk.BuildingBlocks.Domain;
public interface IRepositoryBase<TDomain, TId>
where TDomain : Entity<TId> where TId : ITypeIdValueBase
public interface IRepositoryBase<T,TId>
where T : Entity<TId> where TId : ITypeIdValueBase
{
Task<TDomain> CreateAsync(TDomain entity, CancellationToken cancellationToken = default);
Task<TDomain> UpdateAsync(TDomain entity, CancellationToken cancellationToken = default);
Task DeleteAsync(TId id, CancellationToken cancellationToken = default);
Task<TDomain> GetByIdAsync(TId id, CancellationToken cancellationToken = default);
Task<T> AddAsync(T entity,CancellationToken cancellationToken = default);
Task UpdateAsync(T entity,CancellationToken cancellationToken = default);
Task DeleteAsync(T entity,CancellationToken cancellationToken = default);
Task DeleteById(TId id,CancellationToken cancellationToken = default);
Task<T?> GetById(TId id,CancellationToken cancellationToken = default);
}
@@ -1,4 +1,6 @@
namespace VizhTalk.BuildingBlocks.Domain;
using System;
namespace VizhTalk.BuildingBlocks.Domain;
public abstract class TypedIdValueBase<T> : IEquatable<TypedIdValueBase<T>> , ITypeIdValueBase
{
@@ -35,6 +37,11 @@ public abstract class TypedIdValueBase<T> : IEquatable<TypedIdValueBase<T>> , IT
return EqualityComparer<T>.Default.Equals(this.Value, other.Value);
}
public bool Equals(ITypeIdValueBase? other)
{
throw new NotImplementedException();
}
public static bool operator ==(TypedIdValueBase<T>? obj1, TypedIdValueBase<T>? obj2)
{
if (object.Equals(obj1, null))
@@ -31,6 +31,7 @@ public abstract class ValueObject : IEquatable<ValueObject>
return !(obj1 == obj2);
}
public bool Equals(ValueObject? obj)
{
return Equals(obj as object);
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.BuildingBlocks.Application.Events;
using VizhTalk.BuildingBlocks.Application.Outbox;
using VizhTalk.BuildingBlocks.Domain;
@@ -1,5 +1,5 @@
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
@@ -8,12 +8,12 @@ public class UnitOfWorkCommandHandlerDecorator<TCommand> : ICommandHandler<TComm
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _decorated;
private readonly IUnitOfWork _unitOfWork;
private readonly IUnitOfWorkBase _unitOfWork;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWorkCommandHandlerDecorator(
ICommandHandler<TCommand> decorated,
IUnitOfWork unitOfWork,
IUnitOfWorkBase unitOfWork,
IDomainEventsDispatcher domainEventsDispatcher)
{
_decorated = decorated;
@@ -34,12 +34,12 @@ public class UnitOfWorkCommandHandlerDecorator<TCommand,TResponse> : ICommandHan
where TCommand : ICommand<TResponse>
{
private readonly ICommandHandler<TCommand, TResponse> _decorated;
private readonly IUnitOfWork _unitOfWork;
private readonly IUnitOfWorkBase _unitOfWork;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWorkCommandHandlerDecorator(
ICommandHandler<TCommand, TResponse> decorated,
IUnitOfWork unitOfWork,
IUnitOfWorkBase unitOfWork,
IDomainEventsDispatcher domainEventsDispatcher)
{
_decorated = decorated;
@@ -1,5 +1,6 @@
using Humanizer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System.Reflection;
using VizhTalk.BuildingBlocks.Domain;
@@ -7,11 +8,16 @@ namespace VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers
{
public static class Extensions
{
public static ModelBuilder FindEntities(this ModelBuilder modelBuilder)
public static ModelBuilder FindEntities(this ModelBuilder modelBuilder, Assembly assembly)
{
var entityTypes = typeof(Entity<>).Assembly
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && typeof(Entity<>).IsAssignableFrom(t));
var entityTypes = assembly
.GetTypes()
.Where(t =>
t.IsClass &&
!t.IsAbstract &&
t.BaseType != null &&
t.BaseType.IsGenericType &&
t.BaseType.GetGenericTypeDefinition() == typeof(Entity<>));
foreach (var type in entityTypes)
modelBuilder.Entity(type);
@@ -22,8 +28,7 @@ namespace VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var tableName = entityType.ClrType.Name.Pluralize();
modelBuilder.Entity(entityType.ClrType).ToTable(tableName);
entityType.SetTableName(entityType.DisplayName().Pluralize());
}
return modelBuilder;
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.BuildingBlocks.Infrastructure.Exceptions
{
public class EntityNotFoundException(string name) : Exception(name)
{
}
}
@@ -1,7 +0,0 @@
namespace VizhTalk.BuildingBlocks.Infrastructure;
public interface IUnitOfWork
{
Task<int> CommitAsync(
CancellationToken cancellationToken = default);
}
@@ -0,0 +1,66 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Infrastructure.Exceptions;
namespace VizhTalk.BuildingBlocks.Infrastructure
{
public class RepositoryBase<T, TId> : IRepositoryBase<T, TId>
where T : Entity<TId> where TId : ITypeIdValueBase
{
private readonly DbContext _dbContext;
public RepositoryBase(DbContext dbContext)
{
this._dbContext = dbContext;
}
public async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await _dbContext.Set<T>().AddAsync(entity, cancellationToken);
return entity;
}
public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
await DeleteById(entity.Id, cancellationToken);
}
public async Task DeleteById(TId id, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var set = _dbContext.Set<T>();
var entity = await set.FirstOrDefaultAsync(x => x.Id.Equals(id));
if (entity == null)
throw new EntityNotFoundException(set.EntityType.Name);
entity.SetDeleted(true);
}
public async Task<T?> GetById(TId id, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var set = _dbContext.Set<T>();
return await set.FirstOrDefaultAsync(x => x.Id.Equals(id));
}
public Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var set = _dbContext.Set<T>();
set.Update(entity);
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,48 @@
using Microsoft.Data.SqlClient;
using System.Data;
using VizhTalk.BuildingBlocks.Application.Data;
namespace VizhTalk.BuildingBlocks.Infrastructure;
public class SqlConnectionFactory : ISqlConnectionFactory
{
private readonly string _connectionString;
private IDbConnection _connection;
public SqlConnectionFactory(string connectionString)
{
this._connectionString = connectionString;
}
public IDbConnection GetOpenConnection()
{
if (this._connection == null || this._connection.State != ConnectionState.Open)
{
this._connection = new SqlConnection(_connectionString);
this._connection.Open();
}
return this._connection;
}
public IDbConnection CreateNewConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
public string GetConnectionString()
{
return _connectionString;
}
public void Dispose()
{
if (this._connection != null && this._connection.State == ConnectionState.Open)
{
this._connection.Dispose();
}
}
}
@@ -1,14 +1,15 @@
using Microsoft.EntityFrameworkCore;
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
namespace VizhTalk.BuildingBlocks.Infrastructure;
public class UnitOfWork : IUnitOfWork
public class UnitOfWorkBase : IUnitOfWorkBase
{
private readonly DbContext _context;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWork(
public UnitOfWorkBase(
DbContext context,
IDomainEventsDispatcher domainEventsDispatcher)
{
@@ -23,4 +24,10 @@ public class UnitOfWork : IUnitOfWork
return await _context.SaveChangesAsync(cancellationToken);
}
public void Dispose()
{
GC.SuppressFinalize(this);
_context.Dispose();
}
}
@@ -20,4 +20,8 @@
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Application\VizhTalk.BuildingBlocks.Application.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="EF\Configuration\" />
</ItemGroup>
</Project>
@@ -0,0 +1,10 @@
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Gateway.Web.BFF.Mutations;
public class LanguageMutation(LanguageGrpc.LanguageGrpcClient client)
{
[GraphQLName("CreateLanguage")]
public async Task<LanguageDTO> CreateLanguage(CreateLanguageRequest model)
=> await client.CreateAsync(model);
}
+4 -2
View File
@@ -1,3 +1,4 @@
using VizhTalk.Gateway.Web.BFF.Mutations;
using VizhTalk.Gateway.Web.BFF.Queries;
using VizhTalk.Grpc.Learning.Language;
@@ -8,10 +9,11 @@ builder.Services.AddGrpcClient<LanguageGrpc.LanguageGrpcClient>(o =>
});
builder.Services.AddScoped<LanguageQuery>();
builder.Services.AddScoped<LanguageMutation>();
builder.AddGraphQL()
.AddQueryType<LanguageQuery>();
.AddQueryType<LanguageQuery>()
.AddMutationType<LanguageMutation>();
var app = builder.Build();
app.MapGraphQL();
@@ -2,15 +2,14 @@
namespace VizhTalk.Gateway.Web.BFF.Queries
{
public class LanguageQuery(LanguageGrpc.LanguageGrpcClient languageGrpcClient)
public class LanguageQuery(LanguageGrpc.LanguageGrpcClient client)
{
[GraphQLName("Languages")]
public async Task<GetAllLanguageReply> GetAll()
{
return await languageGrpcClient.GetAllAsync(new Empty());
}
[GraphQLName("Language")]
public async Task<Language> GetLanguageByTitle(GetLanguageByTitleRequest request)
=> await languageGrpcClient.GetLanguageByTitleAsync(request);
public async Task<GetAllLanguageDTO> GetAllLanguage()
=> await client.GetAllAsync(new GetAllLanguageRequest());
[GraphQLName("LanguageByTitle")]
public async Task<LanguageDTO> GetLanguageByTitle(GetLanguageByTitleRequest request)
=> await client.GetByTitleAsync(request);
}
}
@@ -0,0 +1,7 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Learning.Application.CommandHandlers.Commands;
public record CreateLanguageCommand(CreateLanguageRequest model) : ICommand<LanguageDTO>;
public record UpdateLanguageCommand(UpdateLanguageRequest model) : ICommand;
@@ -0,0 +1,35 @@
using Mapster;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.Grpc.Learning.Language;
using VizhTalk.Learning.Application.CommandHandlers.Commands;
using VizhTalk.Learning.Domain.Entities;
using VizhTalk.Learning.Domain.ValueObjects;
namespace VizhTalk.Learning.Application.CommandHandlers
{
public class CreateLanguageCommandHandler(IUnitOfWork unitOfWork) : ICommandHandler<CreateLanguageCommand, LanguageDTO>
{
public async Task<LanguageDTO> Handle(CreateLanguageCommand request, CancellationToken cancellationToken)
{
try
{
var language = Language.CreateNew(new LanguageTitle(request.model.Title));
await unitOfWork.Language.AddAsync(language, cancellationToken);
return language.Adapt<LanguageDTO>();
}
catch (Exception ex)
{
throw;
}
}
}
public class UpdateLanguageCommandHandler(IUnitOfWork unitOfWork) : ICommandHandler<UpdateLanguageCommand>
{
public Task Handle(UpdateLanguageCommand request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
@@ -1,7 +1,7 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.Learning.Application;
namespace VizhTalk.Learning.Infrastructure.Configuration;
public interface ILearningModule : IExecutor
{
@@ -0,0 +1,9 @@
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.Learning.Domain.Repositories;
namespace VizhTalk.Learning.Application;
public interface IUnitOfWork : IUnitOfWorkBase
{
public ILanguageRepository Language { get; }
}
@@ -0,0 +1,19 @@
using Mapster;
using VizhTalk.Grpc.Learning.Language;
using VizhTalk.Learning.Domain.Entities;
namespace VizhTalk.Learning.Application.Mappers
{
public class MapperConfig
{
public static void Process()
{
var config = TypeAdapterConfig<Language, LanguageDTO>.NewConfig()
.Map(x => x.Title, des => des.Title.Value)
.Map(x=>x.Id, des=> des.Id == null ? 0 : des.Id.Value);
config.Compile();
}
}
}
@@ -0,0 +1,7 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Learning.Application.QueryHandlers.Queries;
public class GetAllLanguageQuery : IQuery<GetAllLanguageDTO>;
public class GetLanguageByTitleQuery : IQuery<LanguageDTO>;
@@ -8,7 +8,8 @@
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Application\VizhTalk.BuildingBlocks.Application.csproj" />
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Infrastructure\VizhTalk.BuildingBlocks.Infrastructure.csproj" />
<ProjectReference Include="..\VizhTalk.Learning.Domain\VizhTalk.Learning.Domain.csproj" />
<ProjectReference Include="..\VizhTalk.Services.Grpc.Learning\VizhTalk.Grpc.Learning.csproj" />
</ItemGroup>
</Project>
+11 -1
View File
@@ -6,6 +6,16 @@ namespace VizhTalk.Learning.Domain.Entities;
public class Language : Entity<LanguageId>, IAggregateRoot
{
public Language()
{
// only EF
}
private Language(LanguageTitle title)
{
Title = title;
}
public LanguageTitle Title { get; private set; }
public static Language CreateNew(LanguageTitle title)
=> new(title);
}
@@ -0,0 +1,11 @@
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.Learning.Domain.Entities;
using VizhTalk.Learning.Domain.TypeIds;
namespace VizhTalk.Learning.Domain.Repositories
{
public interface ILanguageRepository : IRepositoryBase<Language,LanguageId>
{
}
}
@@ -7,9 +7,9 @@ using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.Learning.Domain.TypeIds
{
public class LanguageId : TypedIdValueBase<LanguageId>
public class LanguageId : TypedIdValueBase<long>
{
public LanguageId(LanguageId value) : base(value)
public LanguageId(long value) : base(value)
{
}
}
@@ -8,6 +8,8 @@ namespace VizhTalk.Learning.Domain.ValueObjects
public LanguageTitle(string title)
{
Guard.NullOrEmpty(title, nameof(LanguageTitle));
Guard.MaxLength(title, nameof(LanguageTitle), 60);
Value = title;
}
public string Value { get; set; }
@@ -5,6 +5,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.Learning.Application;
using VizhTalk.Learning.Domain.Entities;
namespace VizhTalk.Learning.Infrastructure.Configuration
{
@@ -12,5 +13,6 @@ namespace VizhTalk.Learning.Infrastructure.Configuration
{
public static Assembly Infrastructure = typeof(LearningStartup).Assembly;
public static Assembly Application = typeof(ILearningModule).Assembly;
public static Assembly Domain = typeof(Language).Assembly;
}
}
@@ -1,10 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Data;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Configuration.Query;
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.Learning.Application;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
namespace VizhTalk.Learning.Infrastructure.Configuration;
@@ -21,19 +25,34 @@ public class LearningModule(IServiceProvider serviceProvider) : ILearningModule
public async Task<TResult> Execute<TResult>(ICommand<TResult> command, CancellationToken cancellationToken = default)
{
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(command.GetType());
var commandType = command.GetType(); // مثلا CreateUserCommand
var resultType = commandType.GetInterface(typeof(ICommand<>).Name)!.GetGenericArguments()[0];
// حالا handlerType میشه:
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(commandType, resultType);
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicCommand = command;
return await dynamicHandler.Handle(dynamicCommand, cancellationToken);
}
public async Task<TResult> Execute<TResult>(IQuery<TResult> query, CancellationToken cancellationToken = default)
{
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(query.GetType());
var queryType = query.GetType();
var resultType = queryType.GetInterface(typeof(IQuery<>).Name)!.GetGenericArguments()[0];
// حالا handlerType میشه:
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, resultType);
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicQuery = query;
return await dynamicHandler.Handle(dynamicQuery, cancellationToken);
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.Learning.Domain.Entities;
namespace VizhTalk.Learning.Infrastructure.EF.Configurations
{
internal class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> builder)
{
builder.ToTable(nameof(Language), Schema.Learning);
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.ValueGeneratedOnAdd()
.HasConversion(x => x.Value, x => new Domain.TypeIds.LanguageId(x));
builder.Property(x => x.Title)
.HasConversion(s => s.Value, s => new Domain.ValueObjects.LanguageTitle(s))
.HasMaxLength(60);
}
}
}
@@ -1,15 +1,26 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers;
using VizhTalk.Learning.Infrastructure.Configuration;
namespace VizhTalk.Learning.Infrastructure.EF;
public class LearningDbContext : DbContext
{
public LearningDbContext(DbContextOptions<LearningDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.FindEntities()
modelBuilder.HasDefaultSchema(Schema.Learning);
modelBuilder.FindEntities(Assemblies.Domain)
.ApplyConfigurationsFromAssembly(Assemblies.Infrastructure, x => x.FullName.Contains("EF.Configurations"))
.PluralizeEntities();
}
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Infrastructure.EF
{
public class Schema
{
public const string Learning = "Learning";
}
}
@@ -1,14 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using VizhTalk.BuildingBlocks.Application;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Configuration.Query;
using VizhTalk.BuildingBlocks.Application.Data;
using VizhTalk.BuildingBlocks.Application.Events;
using VizhTalk.BuildingBlocks.Application.Outbox;
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
using VizhTalk.Learning.Application;
using VizhTalk.Learning.Application.Mappers;
using VizhTalk.Learning.Infrastructure.Configuration;
using VizhTalk.Learning.Infrastructure.EF;
using VizhTalk.Learning.Infrastructure.Outbox;
namespace VizhTalk.Learning.Infrastructure;
@@ -16,16 +21,21 @@ public static class LearningStartup
{
public static void Initialize(this IServiceCollection services, string connectionString)
{
var domainNotificationsMap = new BiDictionary<string, Type>();
services.AddDbContext<LearningDbContext>(op =>
{
op.UseSqlServer(connectionString);
});
services.AddScoped<DbContext, LearningDbContext>();
services.AddScoped<ILearningModule, LearningModule>();
services.AddScoped<IExecutor, LearningModule>();
services.AddScoped<IUnitOfWorkBase, UnitOfWorkBase>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IDomainEventsAccessor, DomainEventsAccessor>();
services.AddScoped<IDomainEventsDispatcher, DomainEventsDispatcher>();
services.AddScoped<IDomainNotificationsMapper, DomainNotificationsMapper>();
services.AddScoped(sp => domainNotificationsMap);
services.Scan(scan => scan.
FromAssemblies(Assemblies.Application, Assemblies.Infrastructure)
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)), publicOnly: false)
@@ -40,7 +50,11 @@ public static class LearningStartup
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime());
services.Decorate(typeof(ICommandHandler<,>), typeof(UnitOfWorkCommandHandlerDecorator<,>));
services.Decorate(typeof(ICommandHandler<>), typeof(UnitOfWorkCommandHandlerDecorator<>));
MapperConfig.Process();
services.AddScoped(sa =>
{
var notifications = sa.GetServices<IDomainEventNotification>();
@@ -52,9 +66,16 @@ public static class LearningStartup
});
services.AddScoped<IOutbox>(sa =>
{
return null; // ToDo
return new OutboxAccessor(); // ToDo
});
services.AddScoped<ISqlConnectionFactory>(x => new SqlConnectionFactory(connectionString));
}
public static void UseLearningModule(this IApplicationBuilder app)
{
using var scope = app.ApplicationServices.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<LearningDbContext>();
db.Database.Migrate();
}
}
@@ -0,0 +1,54 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using VizhTalk.Learning.Infrastructure.EF;
#nullable disable
namespace VizhTalk.Learning.Infrastructure.Migrations
{
[DbContext(typeof(LearningDbContext))]
[Migration("20250901155327_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Learning")
.HasAnnotation("ProductVersion", "8.0.19")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("VizhTalk.Learning.Domain.Entities.Language", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Languages", "Learning");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace VizhTalk.Learning.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "Learning");
migrationBuilder.CreateTable(
name: "Languages",
schema: "Learning",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Languages", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Languages",
schema: "Learning");
}
}
}
@@ -0,0 +1,51 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using VizhTalk.Learning.Infrastructure.EF;
#nullable disable
namespace VizhTalk.Learning.Infrastructure.Migrations
{
[DbContext(typeof(LearningDbContext))]
partial class LearningDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Learning")
.HasAnnotation("ProductVersion", "8.0.19")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("VizhTalk.Learning.Domain.Entities.Language", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Languages", "Learning");
});
#pragma warning restore 612, 618
}
}
}
@@ -15,12 +15,12 @@ namespace VizhTalk.Learning.Infrastructure.Outbox
}
public void Add(OutboxMessage message)
{
throw new NotImplementedException();
}
public Task Save()
{
throw new NotImplementedException();
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,15 @@
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.Learning.Domain.Entities;
using VizhTalk.Learning.Domain.Repositories;
using VizhTalk.Learning.Domain.TypeIds;
using VizhTalk.Learning.Infrastructure.EF;
namespace VizhTalk.Learning.Infrastructure.Repositories
{
internal class LanguageRepository : RepositoryBase<Language, LanguageId>, ILanguageRepository
{
public LanguageRepository(LearningDbContext dbContext) : base(dbContext)
{
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
using VizhTalk.Learning.Application;
using VizhTalk.Learning.Domain.Repositories;
using VizhTalk.Learning.Infrastructure.EF;
using VizhTalk.Learning.Infrastructure.Repositories;
namespace VizhTalk.Learning.Infrastructure
{
internal class UnitOfWork(LearningDbContext learningDbContext, IDomainEventsDispatcher domainEventsDispatcher)
: UnitOfWorkBase(learningDbContext, domainEventsDispatcher), IUnitOfWork
{
private ILanguageRepository _language;
public ILanguageRepository Language
{
get
{
if (_language == null)
_language = new LanguageRepository(learningDbContext);
return _language;
}
}
}
}
@@ -6,6 +6,18 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.19">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.19">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Infrastructure\VizhTalk.BuildingBlocks.Infrastructure.csproj" />
<ProjectReference Include="..\VizhTalk.Learning.Application\VizhTalk.Learning.Application.csproj" />
+4 -2
View File
@@ -2,15 +2,17 @@ using VizhTalk.Learning.Infrastructure;
using VizhTalk.Learning.Presentation.Services;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("LearningDB")!;
// Add services to the container.
builder.Services.AddGrpc();
builder.Services.Initialize("");
builder.Services.Initialize(connectionString);
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<LanguageService>();
app.UseLearningModule();
app.Run();
@@ -1,27 +1,14 @@
using Google.Protobuf.Collections;
using Grpc.Core;
using Grpc.Core;
using VizhTalk.Grpc.Learning.Language;
using VizhTalk.Learning.Application.CommandHandlers.Commands;
using VizhTalk.Learning.Infrastructure.Configuration;
namespace VizhTalk.Learning.Presentation.Services;
public class LanguageService : LanguageGrpc.LanguageGrpcBase
public class LanguageService(ILearningModule learningModule) : LanguageGrpc.LanguageGrpcBase
{
public static GetAllLanguageReply Languages = new GetAllLanguageReply();
public LanguageService()
public override async Task<LanguageDTO> Create(CreateLanguageRequest request, ServerCallContext context)
{
Languages.Languages.Add(new Language() { Title = "English" });
Languages.Languages.Add(new Language() { Title = "Germany" });
Languages.Languages.Add(new Language() { Title = "Persian" });
Languages.Languages.Add(new Language() { Title = "Spain" });
}
public override async Task<GetAllLanguageReply> GetAll(Empty request, ServerCallContext context)
{
return Languages;
}
public override async Task<Language> GetLanguageByTitle(GetLanguageByTitleRequest request, ServerCallContext context)
{
var language = Languages.Languages.FirstOrDefault(x => x.Title == request.Title);
return language;
return await learningModule.Execute(new CreateLanguageCommand(request), context.CancellationToken);
}
}
@@ -8,11 +8,16 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.19">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.Learning.Infrastructure\VizhTalk.Learning.Infrastructure.csproj" />
<ProjectReference Include="..\VizhTalk.Services.Grpc.Learning\VizhTalk.Grpc.Learning.csproj" />
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema=" " /></VisualStudio></ProjectExtensions>
</Project>
@@ -5,6 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"LearningDB": "Server=.; Database=VizhTalk.Learning; Trusted_Connection=True; MultipleActiveResultSets=true;TrustServerCertificate=True;"
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
+22 -11
View File
@@ -4,21 +4,32 @@ option csharp_namespace="VizhTalk.Grpc.Learning.Language";
package greet;
message Language {
string title = 1;
message LanguageDTO {
string Id = 1;
string title = 2;
}
message GetAllLanguageReply {
repeated Language languages = 1;
message GetAllLanguageDTO{
repeated LanguageDTO language =1;
}
message Empty {}
message GetLanguageByTitleRequest {
string title = 1;
message GetAllLanguageRequest{
}
message GetLanguageByTitleRequest{
LanguageDTO Language = 1;
}
message CreateLanguageRequest{
string title = 2;
}
message UpdateLanguageRequest{
string Id = 1;
string title = 2;
}
message Empty{
}
service LanguageGrpc {
rpc GetAll (Empty) returns (GetAllLanguageReply);
rpc GetLanguageByTitle (GetLanguageByTitleRequest) returns (Language);
rpc GetAll (GetAllLanguageRequest) returns (GetAllLanguageDTO);
rpc GetByTitle (GetLanguageByTitleRequest) returns (LanguageDTO);
rpc Create (CreateLanguageRequest) returns (LanguageDTO);
rpc Update (UpdateLanguageRequest) returns (Empty);
}