Skip to content

مكتبات الخلفية والمعمارية — نظام إدارة المغسلة

معلومات الوثيقة

الحقل القيمة
المشروع نظام إدارة المغسلة
الإصدار 1.0
اللغة العربية
الإطار ASP.NET Core 10 + .NET 10
نوع الوثيقة دليل مكتبات الخلفية للمطورين

1. هيكل الحل

backend/
├── Laundry.sln
├── Laundry.Api/                 # المضيف — Controllers, Middleware, wwwroot
├── Laundry.Application/         # حالات الاستخدام — CQRS, DTOs, Validators
├── Laundry.Domain/              # الكيانات، القيم، الواجهات
├── Laundry.Infrastructure/      # EF Core، المستودعات، المحاسبة، المزامنة، الترخيص
└── Laundry.Tests/               # xUnit — Unit + Integration

2. قائمة حزم NuGet الكاملة

Laundry.Api

الحزمة الغرض
Microsoft.AspNetCore.Authentication.JwtBearer مصادقة JWT
Asp.Versioning.Mvc تعيين إصدارات API
Swashbuckle.AspNetCore Swagger/OpenAPI
Serilog.AspNetCore تسجيل منظم
Serilog.Sinks.Seq تسجيل إلى Seq
Serilog.Sinks.PostgreSQL تسجيل إلى قاعدة البيانات
OpenTelemetry.Extensions.Hosting تتبع موزع
OpenTelemetry.Instrumentation.AspNetCore تتبع HTTP
AspNetCore.HealthChecks.UI لوحة فحوصات الصحة
AspNetCore.HealthChecks.UI.Client عميل لوحة الصحة
AspNetCore.HealthChecks.Npgsql فحص PostgreSQL
AspNetCore.HealthChecks.Uris فحص Traefik/Seq

Laundry.Application

الحزمة الغرض
MediatR نمط CQRS
FluentValidation.AspNetCore التحقق من المدخلات
AutoMapper تحويل DTO

Laundry.Domain

الحزمة الغرض
MediatR.Contracts عقود أحداث النطاق

Laundry.Infrastructure

الحزمة الغرض
Npgsql.EntityFrameworkCore.PostgreSQL موفر PostgreSQL
Microsoft.EntityFrameworkCore.Sqlite SQLite للفروع غير المتصلة
Microsoft.EntityFrameworkCore.Design أدوات CLI
Dapper SQL خام للتقارير
Quartz.Extensions.Hosting مهام خلفية
BCrypt.Net-Next تشفير كلمات المرور
OpenTelemetry.Instrumentation.EntityFrameworkCore تتبع استعلامات EF

Laundry.Tests

الحزمة الغرض
xunit إطار الاختبار
Moq المحاكاة
FluentAssertions تأكيدات قابلة للقراءة
Testcontainers.PostgreSql قاعدة بيانات للاختبارات التكاملية
Microsoft.AspNetCore.Mvc.Testing اختبارات تكاملية لـ Web API
coverlet.collector تغطية الكود

3. طبقات Clean Architecture

Laundry.Api           → مراجع Application + Infrastructure
Laundry.Application   → مراجع Domain فقط
Laundry.Domain        ← لا يرجع شيئاً
Laundry.Infrastructure → يرجع Domain + Application

القاعدة: الاعتماديات تتجه للداخل. Domain نقي تماماً.


4. حقن الاعتماديات

builder.Services.AddScoped<IInvoiceRepository, InvoiceRepository>();
builder.Services.AddScoped<IAccountingRulesEngine, AccountingRulesEngine>();

builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssembly(typeof(ApplicationAssembly).Assembly));
builder.Services.AddValidatorsFromAssembly(typeof(ApplicationAssembly).Assembly);
builder.Services.AddAutoMapper(typeof(ApplicationAssembly).Assembly);

// سلوكيات MediatR (تنفذ بالترتيب):
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));

5. EF Core — Code-First

DbContext

public class LaundryDbContext : DbContext
{
    public DbSet<Invoice> Invoices => Set<Invoice>();
    public DbSet<JournalEntryLine> JournalEntryLines => Set<JournalEntryLine>();
    // ...

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfigurationsFromAssembly(typeof(LaundryDbContext).Assembly);
        // منع التوليد التلقائي لـ UUID (يولدها التطبيق)
        foreach (var entity in builder.Model.GetEntityTypes())
        {
            if (entity.ClrType.GetProperty("Id")?.PropertyType == typeof(Guid))
                entity.FindProperty("Id")!.ValueGenerated = ValueGenerated.Never;
        }
    }
}

تكوين كيان (مثال)

builder.Property(x => x.GrandTotal).HasColumnType("numeric(18,4)");  // مال
builder.Property(x => x.OperationalStatus).HasConversion<string>().HasMaxLength(20);
builder.HasIndex(x => x.InvoiceNumber).IsUnique();

الترحيلات

dotnet ef migrations add InitialCreate -p Laundry.Infrastructure -s Laundry.Api
dotnet ef database update

6. CQRS مع MediatR

أمر

public record CreateInvoiceCommand(...) : IRequest<Result<InvoiceDto>>;

معالج

public class CreateInvoiceHandler : IRequestHandler<CreateInvoiceCommand, Result<InvoiceDto>>
{
    public async Task<Result<InvoiceDto>> Handle(CreateInvoiceCommand req, CancellationToken ct)
    {
        var invoice = Invoice.Create(req.CustomerId, req.BranchId, req.Items);
        await _repo.AddAsync(invoice, ct);
        var entries = _engine.Evaluate("InvoiceCreated", invoice);
        await _uow.AddJournalEntriesAsync(entries, ct);
        await _uow.SaveChangesAsync(ct);
        return Result<InvoiceDto>.Success(_mapper.Map<InvoiceDto>(invoice));
    }
}

استعلام (Dapper للتقارير)

var balance = await _db.QuerySingleOrDefaultAsync<decimal>(@"
    SELECT COALESCE(SUM(debit),0) - COALESCE(SUM(credit),0)
    FROM journal_entry_lines WHERE customer_id = @Id", new { Id });

7. FluentValidation

public class CreateInvoiceValidator : AbstractValidator<CreateInvoiceCommand>
{
    public CreateInvoiceValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Items).NotEmpty().Must(x => x.Count > 0);
        RuleForEach(x => x.Items).SetValidator(new InvoiceItemValidator());
    }
}

يتم التحقق تلقائياً قبل المعالج عبر سلوك MediatR ValidationBehavior.


8. Serilog + OpenTelemetry + Seq

builder.Host.UseSerilog((ctx, cfg) => cfg
    .ReadFrom.Configuration(ctx.Configuration)
    .WriteTo.Console()
    .WriteTo.Seq(ctx.Configuration["SeqServerUrl"]!));

builder.Services.AddOpenTelemetry()
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddEntityFrameworkCoreInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddPrometheusExporter());

9. فحوصات الصحة

builder.Services.AddHealthChecks()
    .AddNpgsql(connectionString)
    .AddUrlGroup(new Uri("http://traefik:8080/ping"), "traefik")
    .AddUrlGroup(new Uri("http://seq:5341/health"), "seq");

app.MapHealthChecks("/health");
app.MapHealthChecksUI(o => o.UIPath = "/health-ui");
app.MapPrometheusScrapingEndpoint("/metrics");

10. اتفاقيات الكود

الاتفاقية القاعدة
التسمية PascalCase للعام. camelCase للمتغيرات. _camelCase للحقول الخاصة.
الملفات صنف واحد في الملف. اسم الملف = اسم النوع.
التزامن كل عمليات I/O غير متزامنة. استخدم CancellationToken.
المال decimal دائماً. numeric(18,4) في PostgreSQL. ممنوع float/double.
UUID كل المفاتيح Guid.NewGuid() من طبقة التطبيق. القاعدة لا تولدها.
المعاملات كل معالج أوامر يغلف في معاملة DB عبر TransactionBehavior.
الأخطاء نمط Result<T> للأخطاء المتوقعة. الاستثناءات للأخطاء غير المتوقعة فقط.

سجل المراجعة

التاريخ الإصدار المعد التغييرات
2026-05-10 1.0 قائد الخلفية الإصدار الأولي لدليل مكتبات الخلفية