Bagikan melalui


Menerapkan model domain layanan mikro dengan .NET

Petunjuk / Saran

Konten ini adalah kutipan dari eBook, Arsitektur Layanan Mikro .NET untuk Aplikasi .NET Kontainer, tersedia di .NET Docs atau sebagai PDF gratis yang dapat diunduh yang dapat dibaca secara offline.

Arsitektur Layanan Mikro .NET untuk Aplikasi .NET Dalam Kontainer: gambar kecil sampul eBook.

Di bagian sebelumnya, prinsip dan pola desain dasar untuk merancang model domain dijelaskan. Sekarang saatnya untuk menjelajahi kemungkinan cara untuk menerapkan model domain dengan menggunakan .NET (kode C# biasa) dan EF Core. Model domain Anda akan terdiri hanya dari kode Anda. Ini hanya akan memiliki persyaratan model EF Core, tetapi tidak memiliki ketergantungan nyata pada EF. Anda tidak boleh memiliki dependensi atau referensi keras ke EF Core atau ORM lainnya dalam model domain Anda.

Struktur model domain dalam Pustaka Standar .NET kustom

Organisasi folder yang digunakan untuk aplikasi referensi eShopOnContainers menunjukkan model DDD untuk aplikasi. Anda mungkin menemukan bahwa organisasi folder yang berbeda lebih jelas mengomunikasikan pilihan desain yang dibuat untuk aplikasi Anda. Seperti yang Anda lihat di Gambar 7-10, dalam model domain pemesanan ada dua agregat, agregat pesanan dan agregat pembeli. Setiap agregat adalah sekelompok entitas domain dan objek nilai, meskipun Anda juga dapat memiliki agregat yang terdiri dari entitas domain tunggal (entitas akar atau akar agregat).

Cuplikan layar proyek Ordering.Domain di Penjelajah Solusi.

Tampilan Penjelajah Solusi untuk proyek Ordering.Domain, memperlihatkan folder AggregatesModel yang berisi folder BuyerAggregate dan OrderAggregate, masing-masing berisi kelas entitasnya, file objek nilai, dan sebagainya.

Gambar 7-10. Struktur model domain untuk pemesanan layanan mikro di eShopOnContainers

Selain itu, lapisan model domain mencakup kontrak repositori (antarmuka) yang merupakan persyaratan infrastruktur model domain Anda. Dengan kata lain, antarmuka ini mengekspresikan repositori apa dan metode yang harus diterapkan lapisan infrastruktur. Sangat penting bahwa implementasi repositori ditempatkan di luar lapisan model domain, di pustaka lapisan infrastruktur, sehingga lapisan model domain tidak "terkontaminasi" oleh API atau kelas dari teknologi infrastruktur, seperti Entity Framework.

Anda juga dapat melihat folder SeedWork yang berisi kelas dasar kustom yang dapat Anda gunakan sebagai basis untuk entitas domain dan objek nilai Anda, sehingga Anda tidak memiliki kode redundan di setiap kelas objek domain.

Menyusun agregat dalam pustaka Standar .NET kustom

Agregat mengacu pada kluster objek domain yang dikelompokkan bersama untuk mencocokkan konsistensi transaksional. Objek tersebut bisa berupa instans dari entitas (salah satunya adalah akar agregat atau entitas akar) serta objek nilai tambahan lainnya.

Konsistensi transaksional berarti bahwa agregat dijamin konsisten dan terbaru di akhir tindakan bisnis. Misalnya, agregat pesanan dari model domain layanan mikro untuk pemesanan eShopOnContainers disusun seperti yang ditunjukkan pada Gambar 7-11.

Cuplikan layar folder OrderAggregate dan kelasnya.

Tampilan terperinci dari folder OrderAggregate: Address.cs adalah objek nilai, IOrderRepository adalah antarmuka repositori, Order.cs adalah akar agregat, OrderItem.cs adalah entitas anak, dan OrderStatus.cs adalah kelas enumerasi.

Gambar 7-11. Agregat pesanan dalam solusi Visual Studio

Jika Anda membuka salah satu file di folder agregat, Anda dapat melihat bagaimana file ditandai sebagai kelas dasar atau antarmuka kustom, seperti objek entitas atau nilai, seperti yang diimplementasikan di folder SeedWork .

Menerapkan entitas domain sebagai kelas POCO

Anda menerapkan model domain di .NET dengan membuat kelas POCO yang mengimplementasikan entitas domain Anda. Dalam contoh berikut, kelas Order didefinisikan sebagai entitas dan juga sebagai root agregat. Karena kelas Order diturunkan dari kelas dasar Entity, kelas tersebut dapat memanfaatkan kembali kode umum yang terkait dengan fungsionalitas entitas. Perlu diingat bahwa kelas dasar dan antarmuka ini didefinisikan oleh Anda dalam proyek model domain, jadi itu adalah kode Anda, bukan kode infrastruktur dari ORM seperti EF.

// COMPATIBLE WITH ENTITY FRAMEWORK CORE 5.0
// Entity is a custom base class with the ID
public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId;

    public OrderStatus OrderStatus { get; private set; }
    private int _orderStatusId;

    private string _description;
    private int? _paymentMethodId;

    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

    public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
            string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
    {
        _orderItems = new List<OrderItem>();
        _buyerId = buyerId;
        _paymentMethodId = paymentMethodId;
        _orderStatusId = OrderStatus.Submitted.Id;
        _orderDate = DateTime.UtcNow;
        Address = address;

        // ...Additional code ...
    }

    public void AddOrderItem(int productId, string productName,
                            decimal unitPrice, decimal discount,
                            string pictureUrl, int units = 1)
    {
        //...
        // Domain rules/logic for adding the OrderItem to the order
        // ...

        var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);

        _orderItems.Add(orderItem);

    }
    // ...
    // Additional methods with domain rules/logic related to the Order aggregate
    // ...
}

Penting untuk dicatat bahwa ini adalah entitas domain yang diimplementasikan sebagai kelas POCO. Ini tidak memiliki dependensi langsung pada Entity Framework Core atau kerangka kerja infrastruktur lainnya. Implementasi ini seperti yang seharusnya ada di DDD, hanya kode C# yang menerapkan model domain.

Selain itu, kelas ini dihiasi dengan antarmuka bernama IAggregateRoot. Antarmuka tersebut adalah antarmuka kosong, kadang-kadang disebut antarmuka penanda, yang digunakan hanya untuk menunjukkan bahwa kelas entitas ini juga merupakan akar agregat.

Antarmuka penanda terkadang dianggap sebagai anti-pola; namun, ini juga merupakan cara yang bersih untuk menandai kelas, terutama ketika antarmuka tersebut mungkin berkembang. Atribut bisa menjadi pilihan lain untuk penanda, tetapi lebih cepat untuk melihat kelas dasar (Entitas) di samping antarmuka IAggregate alih-alih menempatkan penanda atribut Agregat di atas kelas. Ini masalah preferensi, dalam hal apapun.

Memiliki akar agregat berarti bahwa sebagian besar kode yang terkait dengan konsistensi dan aturan bisnis entitas agregat harus diimplementasikan sebagai metode dalam kelas agregat urutan akar (misalnya, AddOrderItem saat menambahkan objek OrderItem ke agregat). Anda tidak boleh membuat atau memperbarui objek OrderItems secara independen atau langsung; kelas AggregateRoot harus menjaga kontrol dan konsistensi operasi pembaruan apa pun terhadap entitas anaknya.

Merangkum data di Entitas Domain

Masalah umum dalam model entitas adalah bahwa mereka mengekspos properti navigasi koleksi sebagai jenis daftar yang dapat diakses publik. Ini memungkinkan pengembang kolaborator untuk memanipulasi konten jenis koleksi ini, yang dapat melewati aturan bisnis penting yang terkait dengan koleksi, mungkin meninggalkan objek dalam keadaan tidak valid. Solusi untuk ini adalah mengekspos akses baca-saja ke koleksi terkait dan secara eksplisit menyediakan metode yang menentukan cara klien dapat memanipulasinya.

Dalam kode sebelumnya, perhatikan bahwa banyak atribut bersifat baca-saja atau privat dan hanya dapat diperbarui oleh metode kelas, sehingga pembaruan apa pun mempertimbangkan invarian dan logika domain bisnis yang ditentukan dalam metode kelas.

Misalnya, mengikuti pola DDD, Anda tidak boleh melakukan hal berikut dari metode handler perintah atau kelas lapisan aplikasi (sebenarnya, seharusnya tidak mungkin bagi Anda untuk melakukannya):

// WRONG ACCORDING TO DDD PATTERNS – CODE AT THE APPLICATION LAYER OR
// COMMAND HANDLERS
// Code in command handler methods or Web API controllers
//... (WRONG) Some code with business logic out of the domain classes ...
OrderItem myNewOrderItem = new OrderItem(orderId, productId, productName,
    pictureUrl, unitPrice, discount, units);

//... (WRONG) Accessing the OrderItems collection directly from the application layer // or command handlers
myOrder.OrderItems.Add(myNewOrderItem);
//...

Dalam hal ini, metode Tambahkan murni operasi untuk menambahkan data, dengan akses langsung ke koleksi OrderItems. Oleh karena itu, sebagian besar logika domain, aturan, atau validasi yang terkait dengan operasi tersebut dengan entitas anak akan tersebar di seluruh lapisan aplikasi (penangan perintah dan pengontrol API Web).

Jika Anda mengelilingi akar agregat, akar agregat tidak dapat menjamin invariannya, validitasnya, atau konsistensinya. Akhirnya Anda akan memiliki kode spaghetti atau kode skrip transaksi.

Untuk mengikuti pola DDD, entitas tidak boleh memiliki setter publik di properti entitas apa pun. Perubahan dalam entitas harus didorong oleh metode-metode eksplisit dengan bahasa universal yang jelas mengenai perubahan yang dilakukan di entitas tersebut.

Selain itu, koleksi dalam entitas (seperti item pesanan) harus berupa properti baca-saja (metode AsReadOnly dijelaskan nanti). Anda harus dapat memperbaruinya hanya dari dalam metode kelas agregat akar atau metode entitas anak.

Seperti yang Anda lihat dalam kode untuk Pesanan sebagai akar agregat, semua setter harus bersifat privat atau hanya dapat dibaca secara eksternal, dengan demikian setiap operasi terhadap data entitas atau entitas anaknya harus dilakukan melalui metode di kelas entitas. Ini mempertahankan konsistensi dengan cara yang terkontrol dan berorientasi objek alih-alih menerapkan kode skrip transaksional.

Cuplikan kode berikut menunjukkan cara yang tepat untuk mengodekan tugas menambahkan objek OrderItem ke agregat Pesanan.

// RIGHT ACCORDING TO DDD--CODE AT THE APPLICATION LAYER OR COMMAND HANDLERS
// The code in command handlers or WebAPI controllers, related only to application stuff
// There is NO code here related to OrderItem object's business logic
myOrder.AddOrderItem(productId, productName, pictureUrl, unitPrice, discount, units);

// The code related to OrderItem params validations or domain rules should
// be WITHIN the AddOrderItem method.

//...

Dalam cuplikan ini, sebagian besar validasi atau logika yang terkait dengan pembuatan objek OrderItem akan berada di bawah kendali akar agregat Pesanan—dalam metode AddOrderItem—terutama validasi dan logika yang terkait dengan elemen lain dalam agregat. Misalnya, Anda mungkin mendapatkan item produk yang sama dengan hasil dari beberapa panggilan ke AddOrderItem. Dalam metode itu, Anda dapat memeriksa item produk dan mengonsolidasikan item produk yang sama ke dalam satu objek OrderItem dengan beberapa unit. Selain itu, jika ada jumlah diskon yang berbeda tetapi ID produknya sama, Anda kemungkinan akan menerapkan diskon yang lebih tinggi. Prinsip ini berlaku untuk logika domain lain untuk objek OrderItem.

Selain itu, operasi OrderItem(params) baru juga akan dikontrol dan dilakukan oleh metode AddOrderItem dari akar agregat Order. Oleh karena itu, sebagian besar logika atau validasi yang terkait dengan operasi tersebut (terutama apa pun yang berdampak pada konsistensi antara entitas anak lain) akan berada di satu tempat dalam akar agregat. Itu adalah tujuan utama dari pola akar agregat.

Saat Anda menggunakan Entity Framework Core 1.1 atau yang lebih baru, entitas DDD dapat diekspresikan dengan lebih baik karena memungkinkan pemetaan ke bidang selain properti. Ini berguna saat melindungi kumpulan entitas anak atau objek nilai. Dengan peningkatan ini, Anda dapat menggunakan bidang privat sederhana alih-alih properti dan Anda dapat menerapkan pembaruan apa pun ke kumpulan bidang dalam metode publik dan menyediakan akses baca-saja melalui metode AsReadOnly.

Di DDD, Anda ingin memperbarui entitas hanya melalui metode di entitas (atau konstruktor) untuk mengontrol invarian dan konsistensi data, sehingga properti hanya didefinisikan dengan aksesor get. Properti didukung oleh bidang privat. Anggota privat hanya dapat diakses dari dalam kelas. Namun, ada satu pengecualian: EF Core perlu mengatur bidang-bidang ini juga (sehingga dapat mengembalikan objek dengan nilai yang tepat).

Memetakan properti hanya dengan mendapatkan aksesor ke bidang dalam tabel database

Memetakan properti ke kolom tabel database bukanlah tanggung jawab domain tetapi bagian dari infrastruktur dan lapisan persistensi. Kami menyebutkan ini di sini hanya agar Anda mengetahui kemampuan baru di EF Core 1.1 atau yang lebih baru terkait dengan bagaimana Anda dapat memodelkan entitas. Detail tambahan tentang topik ini dijelaskan di bagian infrastruktur dan persistensi.

Saat Anda menggunakan EF Core 1.0 atau yang lebih baru, dalam DbContext Anda perlu memetakan properti yang ditentukan hanya dengan getter ke bidang aktual dalam tabel database. Ini dilakukan dengan metode HasField dari kelas PropertyBuilder.

Memetakan bidang tanpa properti

Dengan fitur di EF Core 1.1 atau yang lebih baru untuk memetakan kolom ke bidang, dimungkinkan juga untuk tidak menggunakan properti. Sebagai gantinya, Anda hanya dapat memetakan kolom dari tabel ke bidang. Kasus penggunaan umum untuk ini adalah bidang privat untuk status internal yang tidak perlu diakses dari luar entitas.

Misalnya, dalam contoh kode OrderAggregate sebelumnya, ada beberapa bidang privat, seperti _paymentMethodId bidang , yang tidak memiliki properti terkait untuk setter atau getter. Bidang itu juga dapat dihitung dalam logika bisnis pesanan dan digunakan dari metode pesanan, tetapi perlu dipertahankan dalam database juga. Jadi di EF Core (sejak v1.1), ada cara untuk memetakan bidang tanpa properti terkait ke kolom dalam database. Ini juga dijelaskan di bagian Lapisan infrastruktur dari panduan ini.

Sumber daya tambahan