Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน
จำลองออบเจ็กต์ค่า DTO และเหตุการณ์โดเมนเป็น records เพื่อบังคับใช้ความไม่เปลี่ยนแปลงและทำให้โค้ดเข้าใจได้ชัดเจนขึ้น
Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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เอนทิตีเทียบกับอ็อบเจ็กต์ค่า
เอนทิตีมีเอกลักษณ์ (ติดตามด้วย ID ตลอดช่วงเวลา) ส่วนอ็อบเจ็กต์ค่าไม่มีเอกลักษณ์ และสามารถใช้แทนกันได้หากค่าตรงกัน ควรจำลองสิ่งทั้งสองแตกต่างกันใน 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);DTO และสัญญา API ในรูปแบบเรคคอร์ด
DTO สำหรับคำขอและการตอบกลับมีลักษณะเป็นอ็อบเจ็กต์ค่าโดยธรรมชาติ เพราะใช้ส่งข้อมูลโดยไม่มีเอกลักษณ์ เรคคอร์ดช่วยให้เขียนได้กระชับและไม่เปลี่ยนแปลง
// 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 พร็อพเพอร์ตีของอ็อบเจ็กต์ค่าแต่ละรายการจะถูกจัดเก็บในตารางของเจ้าของ (หรือตารางแยกต่างหาก) โดยไม่มี PK แยกต่างหาก
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);
});รากของ Aggregate ที่ใช้อ็อบเจ็กต์ค่า
นำทุกอย่างมารวมกัน: รากของ Aggregate ของ 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));
}
}การใช้งานจริง: ID ที่ปลอดภัยตามชนิดข้อมูล
ใช้เรคคอร์ดห่อหุ้ม ID แบบพื้นฐาน เพื่อป้องกันการนำ ID ของเอนทิตีต่างชนิดมาใช้ปะปนกันโดยไม่ตั้งใจ ซึ่งเป็นเทคนิค DDD ทั่วไปที่เรียกว่า "ID แบบระบุชนิดอย่างเข้มงวด"
// 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 เพื่อจัดเก็บอ็อบเจ็กต์ค่า
- ID แบบระบุชนิดอย่างเข้มงวดด้วยเรคคอร์ดช่วยป้องกันการนำ ID ของเอนทิตีมาใช้ปะปนกัน
เรียนรู้ C# ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 93
- บทเรียน
- 346
คำถามที่พบบ่อย
บทเรียน “Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน”
จำลองออบเจ็กต์ค่า DTO และเหตุการณ์โดเมนเป็น records เพื่อบังคับใช้ความไม่เปลี่ยนแปลงและทำให้โค้ดเข้าใจได้ชัดเจนขึ้น คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ชนิด Record: พื้นฐานและไวยากรณ์
- ความไม่เปลี่ยนแปลงด้วย init และ with
- ความเท่ากันของค่าและการแยกโครงสร้าง
- Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน