DDD CQRS - otorisasi per-permintaan dan per-perintah

15

Ringkasan

Haruskah otorisasi dalam CQRS / DDD diimplementasikan per-perintah / permintaan atau tidak?

Saya mengembangkan untuk pertama kalinya aplikasi online menggunakan pola DDD CQRS secara ketat. Saya bertemu dengan beberapa masalah, yang tidak bisa saya pikirkan.

Aplikasi yang saya bangun adalah aplikasi buku besar yang memungkinkan orang untuk membuat buku besar, serta memungkinkan orang lain untuk melihat / mengedit / menghapusnya, seperti karyawan. Pembuat buku besar harus dapat mengedit hak akses buku besar yang dibuatnya. Bahkan bisa mengubah kepemilikan. Domain ini memiliki dua agregat, TLedger dan TUser .

Saya membaca banyak posting dengan kata kunci DDD / CQRS mengenai keamanan, otorisasi, dll. Sebagian besar dari mereka menyatakan bahwa otorisasi adalah Subdomain Generik , kecuali ada yang membangun aplikasi keamanan.

Dalam hal ini, domain inti tentu merupakan domain akuntansi yang tertarik dengan transaksi, penyeimbangan, dan akun. Tetapi fungsionalitas untuk dapat mengelola akses berbutir halus ke buku besar juga diperlukan. Saya bertanya-tanya bagaimana merancang ini dalam istilah DDD / CQRS.

Itu dinyatakan dalam tutorial DDD di seluruh tempat bahwa perintah adalah bagian dari bahasa di mana-mana. Mereka bermakna. Itu adalah tindakan nyata yang mewakili "hal yang nyata".

Karena semua perintah dan kueri tersebut adalah tindakan aktual yang akan dijalankan pengguna dalam "kehidupan nyata", haruskah implementasi otorisasi digabungkan dengan semua "perintah" dan "kueri" ini? Seorang pengguna akan memiliki otorisasi untuk menjalankan TLedger.addTransaction () tetapi tidak TLedger.removeTransaction () misalnya. Atau, pengguna akan diizinkan untuk menjalankan kueri "getSummaries ()" tetapi tidak "getTransactions ()".

Pemetaan tiga dimensi akan ada dalam bentuk user-ledger-command atau user-ledger-query untuk menentukan hak akses.

Atau, dengan cara yang dipisahkan, bernama "izin" akan didaftarkan untuk pengguna. Izin yang kemudian akan dipetakan untuk perintah tertentu. Misalnya, izin "ManageTransactions" akan memungkinkan pengguna untuk mengeksekusi "AddTransaction ()", "RemoveTransaction ()", dll.

  1. Izin memetakan pengguna -> buku besar -> perintah / permintaan

  2. Pengguna pemetaan izin -> buku besar -> izin -> perintah / permintaan

Itu bagian pertama dari pertanyaan. Atau secara singkat, haruskah otorisasi dalam CQRS / DDD diimplementasikan per-perintah atau per-permintaan? Atau, haruskah otorisasi dipisahkan dari perintah?

Kedua, tentang otorisasi berdasarkan izin. Seorang Pengguna harus dapat mengelola izin pada Buku Besar atau pada Buku Besar yang diizinkan untuk dikelola.

  1. Perintah manajemen otorisasi terjadi di Buku Besar

Saya berpikir untuk menambahkan peristiwa / perintah / penangan ke dalam agregat Ledger , seperti grantPermission (), revokePermission (), dll. Dalam hal ini, menegakkan aturan-aturan itu akan terjadi pada penangan perintah. Tetapi ini membutuhkan semua perintah untuk menyertakan id dari pengguna yang mengeluarkan perintah itu. Kemudian saya akan memeriksa ke TLedger jika ada izin bagi pengguna untuk menjalankan perintah itu.

Sebagai contoh :

class TLedger{ 
    function addTransactionCmdHandler(cmd){
        if (!this.permissions.exist(user, 'addTransaction')
            throw new Error('Not Authorized');
    }
}
  1. Perintah manajemen otorisasi di Pengguna

Cara sebaliknya adalah dengan memasukkan izin ke dalam Pengguna. TUser akan memiliki seperangkat izin. Kemudian, dalam penangan perintah TLedger, saya akan mengambil pengguna dan memeriksa apakah dia memiliki izin untuk menjalankan perintah. Tapi ini akan mengharuskan saya untuk mengambil agregat TUser untuk setiap perintah TLedger.

class TAddTransactionCmdHandler(cmd) {
    this.userRepository.find(cmd.userId)
    .then(function(user){
        if (!user.can(cmd)){
            throw new Error('Not authorized');
        }
        return this.ledgerRepository.find(cmd.ledgerId);
    })
    .then(function(ledger){
        ledger.addTransaction(cmd);
    })

}
  1. Domain lain dengan layanan

Kemungkinan lain adalah memodelkan domain otorisasi lain sepenuhnya. Domain ini akan tertarik pada hak akses, otorisasi dll. Subdomain akuntansi kemudian akan menggunakan layanan untuk mengakses domain otorisasi ini dalam bentuk AuthorizationService.isAuthorized(user, command).

class TAddTransactionCmdHandler(cmd) {
    authService.isAuthorized(cmd)
    .then(function(authorized){
        if (!authorized) throw new Error('Not authorized');
        return this.ledgerRepository.find(cmd.ledgerId)
    })
    .then(function(){
        ledger.addTransaction(cmd);
    })

}

Keputusan apa yang akan menjadi cara paling "DDD / CQRS"?

Ludovic C
sumber
1
Pertanyaan bagus - Saya sudah mencoba untuk mengatasi masalah yang sama dan tidak ada literatur yang tampaknya mengatasinya secara langsung. Saya agak bingung dengan bagian kedua dari pertanyaan Anda. Itu terdengar seperti Anda bertanya-tanya tentang di mana menempatkan manajemen izin (menambah atau menghapus izin) tetapi contoh yang ditampilkan adalah untuk menambahkan transaksi, jadi sepertinya paruh kedua bertanya "bagaimana saya harus meminta izin". Bisakah Anda menjelaskan bagian itu?
emragins
Setiap transaksi dapat memiliki kebijakan eksekusi. Setiap pengguna harus memiliki satu grup lagi, setiap grup akan memiliki profil akses yang menentukan transaksi mana yang diperbolehkan. Pada saat run-time, sebelum melakukan transaksi, kebijakan diperiksa terhadap profil agregat untuk pengguna yang mengeksekusi. Tentu saja, ini lebih mudah diucapkan daripada dilakukan.
NoChance

Jawaban:

5

Untuk pertanyaan pertama saya telah berjuang dengan sesuatu yang serupa. Semakin banyak saya condong ke arah skema otorisasi tiga fase:

1) Otorisasi pada tingkat perintah / permintaan "apakah pengguna ini pernah memiliki izin untuk menjalankan perintah ini?" Dalam aplikasi MVC ini mungkin bisa ditangani di tingkat controller, tapi saya memilih pre-handler generik yang akan meminta izin toko berdasarkan pada pengguna saat ini dan perintah eksekusi.

2) Otorisasi di dalam layanan aplikasi "apakah pengguna ini" pernah * memiliki izin untuk mengakses entitas ini? "Dalam kasus saya ini mungkin akan berakhir menjadi pemeriksaan implisit hanya dengan menggunakan filter pada repositori - di domain saya ini adalah pada dasarnya TenantId dengan sedikit lebih granularity dari OrganizationId.

3) Otorisasi yang bergantung pada properti sementara dari entitas Anda (seperti Status) akan ditangani di dalam domain. (Kel. "Hanya orang-orang tertentu yang dapat memodifikasi buku besar yang ditutup.") Saya memilih untuk meletakkannya di dalam domain karena sangat bergantung pada domain dan logika bisnis dan saya tidak terlalu nyaman mengeksposnya di tempat lain.

Saya ingin mendengar tanggapan orang lain terhadap ide ini - sobek-sobek jika Anda mau (cukup sediakan beberapa alternatif jika Anda melakukannya :))

emragin
sumber
Saya pikir Anda memiliki beberapa poin valid mengenai "lapisan" otorisasi yang berbeda. Sebuah sistem yang sedang saya kerjakan memiliki berbagai jenis pengguna - pengguna terdaftar dan anggota staf. Izin penangan perintah / permintaan melakukan pemeriksaan dasar pada jenis pengguna. Jika itu adalah staf, selalu melalui. Jika itu adalah pengguna terdaftar maka itu hanya diizinkan jika persyaratan tertentu dipenuhi (misalnya, izin pada agregat).
Magnus
0

Saya akan menerapkan otorisasi sebagai bagian dari BC Otorisasi Anda tetapi menggunakannya sebagai filter tindakan ke dalam sistem Buku Besar Anda. Dengan cara ini, mereka dapat dipisahkan secara logis dari satu sama lain - kode Buku Besar Anda seharusnya tidak harus memanggil kode Otorisasi - tetapi Anda masih mendapatkan otorisasi dalam proses kinerja tinggi dari setiap permintaan yang masuk.

pnschofield
sumber