CQRS tanpa DDD dan tanpa (atau dengan?) ES - apa itu model tulis dan apa model yang dibaca?

11

Sejauh yang saya mengerti, ide besar di balik CQRS adalah memiliki 2 model data yang berbeda untuk menangani perintah dan permintaan. Ini disebut "model tulis" dan "model baca".

Mari kita perhatikan contoh klon aplikasi Twitter. Berikut adalah perintahnya:

  • Pengguna dapat mendaftar sendiri. CreateUserCommand(string username)memancarkanUserCreatedEvent
  • Pengguna dapat mengikuti pengguna lain. FollowUserCommand(int userAId, int userBId)memancarkanUserFollowedEvent
  • Pengguna dapat membuat posting. CreatePostCommand(int userId, string text)memancarkanPostCreatedEvent

Sementara saya menggunakan istilah "acara" di atas, saya tidak bermaksud acara 'sumber acara'. Maksud saya sinyal yang memicu pembaruan model baca. Saya tidak punya toko acara dan sejauh ini ingin berkonsentrasi pada CQRS sendiri.

Dan inilah pertanyaannya:

  • Seorang pengguna perlu melihat daftar postingannya. GetPostsQuery(int userId)
  • Seorang pengguna perlu melihat daftar pengikutnya. GetFollowersQuery(int userId)
  • Seorang pengguna perlu melihat daftar pengguna yang diikuti. GetFollowedUsersQuery(int userId)
  • Seorang pengguna perlu melihat "umpan teman" - log dari semua aktivitas teman mereka ("teman Anda John baru saja membuat posting baru"). GetFriedFeedRecordsQuery(int userId)

Untuk menangani CreateUserCommandsaya perlu tahu apakah pengguna seperti itu sudah ada. Jadi, pada titik ini saya tahu bahwa model penulisan saya harus memiliki daftar semua pengguna.

Untuk menangani FollowUserCommandsaya perlu tahu apakah userA sudah mengikuti userB atau tidak. Pada titik ini saya ingin model tulis saya memiliki daftar semua koneksi pengguna-mengikuti-pengguna.

Dan akhirnya, untuk menangani CreatePostCommandsaya tidak berpikir saya perlu yang lain, karena saya tidak punya perintah seperti UpdatePostCommand. Jika saya punya itu, saya perlu memastikan bahwa posting ada, jadi saya perlu daftar semua posting. Tetapi karena saya tidak memiliki persyaratan ini, saya tidak perlu melacak semua posting.

Pertanyaan # 1 : apakah benar menggunakan istilah "model tulis" seperti yang saya gunakan? Atau apakah "menulis model" selalu berarti "event store" untuk ES? Jika demikian, apakah ada jenis pemisahan antara data yang saya butuhkan untuk menangani perintah dan data yang saya butuhkan untuk menangani pertanyaan?

Untuk menangani GetPostsQuery, saya perlu daftar semua posting. Ini berarti bahwa model baca saya harus memiliki daftar semua posting. Saya akan mempertahankan model ini dengan mendengarkan PostCreatedEvent.

Untuk menangani keduanya GetFollowersQuerydan GetFollowedUsersQuery, saya perlu daftar semua koneksi antara pengguna. Untuk mempertahankan model ini saya akan mendengarkan UserFollowedEvent. Berikut adalah Pertanyaan # 2 : apakah bisa dibilang OK jika saya menggunakan daftar koneksi model tulis di sini? Atau haruskah saya membuat model baca yang lebih baik, karena di masa depan saya mungkin memerlukannya untuk memiliki lebih banyak detail daripada model tulis?

Akhirnya, untuk menangani GetFriendFeedRecordsQuerysaya perlu:

  • Mendengarkan UserFollowedEvent
  • Mendengarkan PostCreatedEvent
  • Ketahui pengguna mana yang mengikuti pengguna lain

Jika pengguna A mengikuti pengguna B dan pengguna B mulai mengikuti pengguna C, catatan berikut ini akan muncul:

  • Untuk pengguna A: "Anda teman pengguna B baru saja mulai mengikuti pengguna C"
  • Untuk pengguna B: "Anda baru saja mulai mengikuti pengguna C"
  • Untuk pengguna C: "Pengguna B sekarang mengikuti Anda"

Inilah Pertanyaan # 3 : model apa yang harus saya gunakan untuk mendapatkan daftar koneksi? Haruskah saya menggunakan model tulis? Haruskah saya menggunakan model baca - GetFollowersQuery/ GetFollowedUsersQuery? Atau haruskah saya membuat GetFriendFeedRecordsQuerymodel itu sendiri menangani UserFollowedEventdan mempertahankan daftar semua koneksi sendiri?

Andrey Agibalov
sumber
Perlu diingat bahwa dalam sistem CQRS, model data Query dan model data Command mungkin mengkonsumsi data dari basis data yang berbeda. Dan kedua model mungkin hidup secara independen satu sama lain (aplikasi yang berbeda). Yang sedang berkata, jawabannya adalah "tergantung" (seperti biasa). Mungkin menarik
Laiv

Jawaban:

7

Greg Young (2010)

CQRS hanyalah penciptaan dua objek di mana sebelumnya hanya ada satu.

Jika Anda berpikir dalam hal Pemisahan Perintah Bertrand Meyer , Anda dapat menganggap model memiliki dua antarmuka yang berbeda, satu yang mendukung perintah, dan satu yang mendukung permintaan.

interface IChangeTheModel {
    void createUser(string username)
    void followUser(int userAId, int userBId)
    void createPost(int userId, string text)
}

interface IDontChangeTheModel {
    Iterable<Post> getPosts(int userId)
    Iterable<Follower> getFollowers(int userId)
    Iterable<FollowedUser> getFollowedUsers(int userId)
}

class TheModel implements IChangeTheModel, IDontChangeTheModel {
    // ...
}

Wawasan Greg Young adalah bahwa Anda dapat memisahkan ini menjadi dua objek yang terpisah

class WriteModel implements IChangeTheModel { ... }
class ReadModel  implements IDontChangeTheModel {...}

Setelah memisahkan objek, Anda sekarang memiliki opsi untuk memisahkan struktur data yang menyimpan status objek dalam memori, sehingga Anda dapat mengoptimalkan untuk setiap kasus; atau menyimpan / mempertahankan status baca secara terpisah dari kondisi penulisan.

Pertanyaan # 1: apakah benar menggunakan istilah "model tulis" seperti yang saya gunakan? Atau apakah "menulis model" selalu berarti "event store" untuk ES? Jika demikian, apakah ada jenis pemisahan antara data yang saya butuhkan untuk menangani perintah dan data yang saya butuhkan untuk menangani pertanyaan?

Istilah WriteModel biasanya dipahami sebagai representasi model yang bisa berubah (yaitu: objek, bukan toko persistensi).

TL; DR: Saya pikir penggunaan Anda baik-baik saja.

Berikut adalah Pertanyaan # 2: apakah bisa dibilang OK jika saya menggunakan daftar koneksi model tulis di sini?

Itu "baik" - ish. Secara konsep, tidak ada yang salah dengan model baca dan tulis yang berbagi struktur yang sama.

Dalam praktiknya, karena menulis ke model biasanya bukan atom, ada potensi masalah ketika satu utas sedang mencoba untuk mengubah keadaan model sementara utas kedua sedang mencoba untuk membacanya.

Inilah Pertanyaan # 3: model apa yang harus saya gunakan untuk mendapatkan daftar koneksi? Haruskah saya menggunakan model tulis? Haruskah saya menggunakan model baca - GetFollowersQuery / GetFollowedUsersQuery? Atau haruskah saya membuat model GetFriendFeedRecordsQuery itu sendiri menangani UserFollowedEvent dan mempertahankan daftar semua koneksi sendiri?

Menggunakan model tulis adalah jawaban yang salah.

Menulis beberapa model baca, di mana masing-masing disetel ke use case tertentu benar-benar masuk akal. Kami mengatakan "model baca", tetapi dipahami bahwa mungkin ada banyak model baca, masing-masing dioptimalkan untuk kasus penggunaan tertentu, dan tidak menerapkan kasus di mana itu tidak masuk akal.

Misalnya, Anda mungkin memutuskan untuk menggunakan penyimpanan nilai kunci untuk mendukung beberapa permintaan, dan basis data grafik untuk permintaan lain, atau basis data relasional di mana model kueri itu masuk akal. Kuda untuk kursus.

Dalam keadaan spesifik Anda, di mana Anda masih mempelajari polanya, saran saya adalah menjaga desain Anda "sederhana" - minta satu model baca yang tidak berbagi struktur data model tulis.

VoiceOfUnreason
sumber
1

Saya akan mendorong Anda untuk mempertimbangkan bahwa Anda memiliki satu model data konseptual.

Kemudian model tulis adalah perwujudan dari model data yang dioptimalkan untuk pembaruan transaksional. Terkadang ini berarti database relasional yang dinormalisasi.

Model baca adalah perwujudan dari model data yang sama yang dioptimalkan untuk melakukan kueri yang dibutuhkan aplikasi Anda. Mungkin masih berupa basis data relasional meskipun sengaja didenormalisasi untuk menangani pertanyaan dengan lebih sedikit gabungan.


(# 1) Untuk CQRS, model tulis tidak harus menjadi toko acara.

(# 2) Saya tidak akan mengharapkan model baca untuk menyimpan apa pun yang tidak ada dalam model tulis, untuk satu karena dalam CQRS, tidak ada yang memperbarui model baca kecuali mekanisme penerusan yang membuat model baca tetap sinkron dengan perubahan pada menulis model.

(# 3) Dalam CQRS, kueri harus bertentangan dengan model baca. Anda dapat melakukan yang berbeda: tidak apa-apa, hanya saja tidak mengikuti CQRS.


Singkatnya, hanya ada satu model data konseptual. CQRS memisahkan jaringan perintah dan kemampuan dari jaringan permintaan dan kemampuan. Mengingat bahwa pemisahan model tulis dan model baca dapat dihosting menggunakan teknologi yang sangat berbeda sebagai pengoptimalan kinerja.

Erik Eidt
sumber