C# Academy · درس

Records في التصميم المستند إلى النطاق

نمذجوا كائنات القيم وDTOs وأحداث النطاق بصيغة records لفرض عدم قابلية التغيير وتحسين وضوح الشيفرة.

الدرس 4 من 412 خطوة

Records في التصميم المستند إلى النطاق درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

DDD وكائنات القيمة

في التصميم الموجّه بالمجال (DDD)، يُعرَّف كائن القيمة بخصائصه لا بهويته. فقيمتا Money(100, "USD") قابلتان للاستبدال. وتُعد السجلات التمثيل المثالي لكائنات القيمة في C#.

كائنات القيمة كسجلات

مثّل كائنات القيمة في المجال كسجلات. فالمساواة البنيوية وعدم القابلية للتغيير وبنية الصياغة الموجزة تتوافق تمامًا مع مبادئ DDD.

// Value objects — defined by their values, not identity
public record Money(decimal Amount, string Currency);
public record Email(string Value);
public record PhoneNumber(string CountryCode, string Number);
public record Address(string Street, string City, string Country, string PostalCode);

// Two Money objects with same values are equal:
var price = new Money(99.99m, "USD");
var same  = new Money(99.99m, "USD");
Console.WriteLine(price == same); // True

التحقق من الصحة في منشئات كائنات القيمة

أضف منشئًا مختصرًا للتحقق من الثوابت. يجب ألا تكون كائنات القيمة قابلة للإنشاء إلا في حالة صالحة — ويجب أن تؤدي الحالة غير الصالحة إلى طرح استثناء وقت الإنشاء.

public record Email(string Value)
{
    // Compact constructor for validation
    public Email : this(Value)
    {
        if (string.IsNullOrWhiteSpace(Value) || !Value.Contains('@'))
            throw new ArgumentException("Invalid email address", nameof(Value));
        Value = Value.Trim().ToLowerInvariant();
    }
}

// Usage:
var email = new Email("  Alice@Example.COM  "); // normalized to alice@example.com
new Email("not-an-email"); // throws ArgumentException

الكيانات مقابل كائنات القيمة

لدى الكيانات هوية (يتتبعها المعرّف عبر الزمن)، بينما لا تملك كائنات القيمة هوية، ويمكن استبدالها إذا تطابقت القيم. نمذج النوعين بشكل مختلف في C#.

// ENTITY: has identity, mutable state
public class Customer
{
    public Guid Id { get; } = Guid.NewGuid();
    public string Name { get; set; } = "";
    public Email Email { get; set; } = default!; // value object
}

// VALUE OBJECT: no identity, immutable
public record Email(string Value);
public record Money(decimal Amount, string Currency);

// Two customers with same name are DIFFERENT entities
// Two Money(100, "USD") are the SAME value

كائنات قيمة غنية بأساليب المجال

يمكن أن تحتوي كائنات القيمة على منطق المجال في صورة أساليب. وتُعيد العمليات كائنات قيمة جديدة بدلًا من تعديل الكائن الموجود.

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new DomainException("Cannot add different currencies");
        return this with { Amount = Amount + other.Amount };
    }

    public Money Subtract(Money other) =>
        Amount >= other.Amount
            ? this with { Amount = Amount - other.Amount }
            : throw new DomainException("Insufficient funds");

    public Money ApplyDiscount(decimal percent) =>
        this with { Amount = Amount * (1 - percent / 100) };

    public static Money Zero(string currency) => new(0, currency);
}

أحداث المجال كسجلات

تصف أحداث المجال أشياء حدثت في المجال. والسجلات مناسبة لها تمامًا: فهي غير قابلة للتغيير ووصفية، وتستخدم المساواة البنيوية، مما يبسّط الاختبار.

// Domain events as records
public abstract record DomainEvent(DateTime OccurredAt);

public record OrderPlaced(
    Guid OrderId,
    Guid CustomerId,
    Money Total,
    DateTime OccurredAt) : DomainEvent(OccurredAt);

public record OrderShipped(
    Guid OrderId,
    string TrackingNumber,
    DateTime OccurredAt) : DomainEvent(OccurredAt);

public record PaymentReceived(
    Guid OrderId,
    Money Amount,
    DateTime OccurredAt) : DomainEvent(OccurredAt);

DTOs وعقود API كسجلات

تُعد DTOs الخاصة بالطلبات والاستجابات كائنات قيمة بطبيعتها — فهي تحمل البيانات دون هوية. وتجعلها السجلات موجزة وغير قابلة للتغيير.

// Request DTOs
public record CreateOrderCommand(
    Guid CustomerId,
    IReadOnlyList<OrderLineDto> Lines);

public record OrderLineDto(Guid ProductId, int Quantity);

// Response DTOs
public record OrderCreatedResponse(
    Guid OrderId,
    string Status,
    decimal Total,
    DateTime CreatedAt);

// These can be compared in tests by value:
var expected = new OrderCreatedResponse(id, "Pending", 99m, now);
Assert.Equal(expected, actual);

تخزين كائنات القيمة في EF Core

استخدم الكيانات المملوكة لتخزين كائنات القيمة في EF Core. تُخزَّن خصائص كل كائن قيمة في جدول المالك (أو في جدول منفصل) دون مفتاح أساسي مستقل.

public class Order
{
    public int Id { get; set; }
    public Money Total { get; set; } = new(0, "USD");
    public Address ShipTo { get; set; } = default!;
}

// EF Core config:
modelBuilder.Entity<Order>(e =>
{
    e.OwnsOne(o => o.Total, money =>
    {
        money.Property(m => m.Amount).HasColumnType("decimal(18,2)");
        money.Property(m => m.Currency).HasMaxLength(3);
    });
    e.OwnsOne(o => o.ShipTo);
});

جذر التجميع مع كائنات القيمة

لنجمع كل ذلك معًا: جذر تجميع Order يستخدم السجلات لكائنات القيمة وأحداث المجال، مع فرض جميع قواعد العمل داخل الأساليب.

public class Order
{
    private readonly List<DomainEvent> _events = new();
    public IReadOnlyList<DomainEvent> DomainEvents => _events;

    public Guid Id { get; } = Guid.NewGuid();
    public Address ShipTo { get; private set; } = default!; // value obj
    public Money Total { get; private set; } = Money.Zero("USD");

    public void PlaceOrder(Address shipTo, IEnumerable<OrderLine> lines)
    {
        ShipTo = shipTo;
        Total  = lines.Aggregate(Money.Zero("USD"),
            (acc, l) => acc.Add(l.Price));
        _events.Add(new OrderPlaced(Id, CustomerId, Total, DateTime.UtcNow));
    }
}

من الواقع العملي: المعرّفات الآمنة نوعيًا

استخدم السجلات لتغليف المعرّفات البدائية ومنع الخلط غير المقصود بين معرّفات الكيانات المختلفة — وهي تقنية شائعة في DDD تُسمى "Strongly Typed IDs".

// Strongly typed IDs — can't confuse CustomerId with OrderId
public record CustomerId(Guid Value)
{
    public static CustomerId New() => new(Guid.NewGuid());
    public static implicit operator Guid(CustomerId id) => id.Value;
}

public record OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
}

// Type safety:
void ShipOrder(OrderId orderId, CustomerId customerId) { }

ShipOrder(OrderId.New(), CustomerId.New()); // OK
ShipOrder(CustomerId.New(), OrderId.New()); // COMPILE ERROR!

اختبار سريع

ما الذي يعرّف كائن القيمة في DDD، ولماذا تُعد السجلات مناسبة له بطبيعتها؟

مراجعة: السجلات في التصميم الموجّه بالمجال

أهم النقاط المستخلصة:

  • كائنات القيمة: تُعرَّف بالقيم ولا تملك هوية — استخدم السجلات
  • الكيانات: تملك هوية (ID) وحالة قابلة للتغيير — استخدم الأصناف
  • تحقق من الثوابت في المنشئات المختصرة — فالحالة غير الصالحة غير قابلة للتمثيل
  • أحداث المجال كسجلات: غير قابلة للتغيير وقابلة للمقارنة وتوثّق نفسها
  • استخدم الكيانات المملوكة في EF Core لتخزين كائنات القيمة
  • تمنع المعرّفات المكتوبة بقوة باستخدام السجلات الخلط بين معرّفات الكيانات
البدء مجانًا

تعلم C# مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
93
الدروس
346

الأسئلة الشائعة

هل درس «Records في التصميم المستند إلى النطاق» مجاني؟

نعم — نص درس «Records في التصميم المستند إلى النطاق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «Records في التصميم المستند إلى النطاق»؟

نمذجوا كائنات القيم وDTOs وأحداث النطاق بصيغة records لفرض عدم قابلية التغيير وتحسين وضوح الشيفرة. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «Records في التصميم المستند إلى النطاق»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أنواع Record: الأساسيات وبناء الجملة
  2. عدم قابلية التغيير باستخدام init وwith
  3. مساواة القيم والتفكيك
  4. Records في التصميم المستند إلى النطاق
← العودة إلى C# Academy