The Arsitektur Bersih menyarankan untuk membiarkan kasus penggunaan interactor memanggil implementasi aktual dari presenter (yang disuntikkan, berikut DIP) untuk menangani respon / display. Namun, saya melihat orang-orang mengimplementasikan arsitektur ini, mengembalikan data keluaran dari interaktor, dan kemudian membiarkan pengontrol (di lapisan adaptor) memutuskan bagaimana menanganinya. Apakah solusi kedua bocor tanggung jawab aplikasi keluar dari lapisan aplikasi, selain tidak secara jelas mendefinisikan port input dan output ke interaktor?
Port input dan output
Mempertimbangkan definisi Arsitektur Bersih , dan terutama diagram alir kecil yang menggambarkan hubungan antara pengontrol, use case berinteraksi, dan presenter, saya tidak yakin apakah saya benar memahami apa yang seharusnya "Use Case Output Port" seharusnya.
Arsitektur bersih, seperti arsitektur heksagonal, membedakan antara porta primer (metode) dan porta sekunder (interface yang akan diimplementasikan oleh adaptor). Mengikuti aliran komunikasi, saya berharap "Use Case Input Port" menjadi port utama (dengan demikian, hanya sebuah metode), dan "Use Case Output Port" sebuah antarmuka untuk diimplementasikan, mungkin argumen konstruktor yang mengambil adaptor yang sebenarnya, sehingga berinteraksi dapat menggunakannya.
Contoh kode
Untuk membuat contoh kode, ini bisa menjadi kode pengontrol:
Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();
Antarmuka presenter:
// Use Case Output Port
interface Presenter
{
public void present(Data data);
}
Akhirnya, interaktor itu sendiri:
class UseCase
{
private Repository repository;
private Presenter presenter;
public UseCase(Repository repository, Presenter presenter)
{
this.repository = repository;
this.presenter = presenter;
}
// Use Case Input Port
public void doSomething()
{
Data data = this.repository.getData();
this.presenter.present(data);
}
}
Di interogator atau memanggil presenter
Interpretasi sebelumnya tampaknya dikonfirmasi oleh diagram yang disebutkan di atas, di mana hubungan antara pengontrol dan port input diwakili oleh panah padat dengan kepala "tajam" (UML untuk "asosiasi", yang berarti "memiliki", di mana controller "memiliki" use case), sedangkan hubungan antara presenter dan port output diwakili oleh panah padat dengan kepala "putih" (UML untuk "warisan", yang bukan yang untuk "implementasi", tetapi mungkin itu artinya).
Lebih jauh, dalam jawaban untuk pertanyaan lain ini , Robert Martin menggambarkan dengan tepat kasus penggunaan di mana interkom memanggil presenter atas permintaan baca:
Mengklik pada peta menyebabkan placePinController dipanggil. Ini mengumpulkan lokasi klik, dan data kontekstual lainnya, membangun struktur data placePinRequest dan meneruskannya ke PlacePinInteractor yang memeriksa lokasi pin, memvalidasi jika perlu, membuat entitas Place untuk merekam pin, membangun EditPlaceReponse objek dan meneruskannya ke EditPlacePresenter yang menampilkan layar editor tempat.
Untuk membuat ini bermain baik dengan MVC, saya bisa berpikir bahwa logika aplikasi yang secara tradisional akan masuk ke controller, di sini dipindahkan ke interaktor, karena kami tidak ingin ada logika aplikasi bocor di luar lapisan aplikasi. Pengontrol di lapisan adaptor hanya akan memanggil interaksor, dan mungkin melakukan beberapa konversi format data kecil dalam proses:
Perangkat lunak pada lapisan ini adalah seperangkat adaptor yang mengubah data dari format yang paling nyaman untuk kasus penggunaan dan entitas, ke format yang paling nyaman untuk beberapa agensi eksternal seperti Database atau Web.
dari artikel asli, berbicara tentang Adaptor Antarmuka.
Pada interaktor mengembalikan data
Namun, masalah saya dengan pendekatan ini adalah bahwa use case harus mengurus presentasi itu sendiri. Sekarang, saya melihat bahwa tujuan dari Presenter
antarmuka adalah untuk menjadi cukup abstrak untuk mewakili beberapa jenis presenter (GUI, Web, CLI, dll.), Dan itu benar-benar hanya berarti "output", yang merupakan sesuatu yang mungkin digunakan sangat baik, tapi tetap saja saya tidak sepenuhnya percaya diri dengan itu.
Sekarang, melihat-lihat aplikasi Web untuk arsitektur bersih, di sekitar saya hanya menemukan orang-orang menafsirkan port output sebagai metode mengembalikan beberapa DTO. Ini akan menjadi sesuatu seperti:
Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious
Ini menarik karena kami memindahkan tanggung jawab untuk "memanggil" presentasi keluar dari use case, jadi use case tidak peduli dengan mengetahui apa yang harus dilakukan dengan data lagi, bukan hanya dengan menyediakan data. Juga, dalam hal ini kita masih tidak melanggar aturan dependensi, karena use case masih tidak tahu apa-apa tentang lapisan luar.
Namun, use case tidak mengontrol saat ketika presentasi yang sebenarnya dilakukan lagi (yang mungkin berguna, misalnya untuk melakukan hal-hal tambahan pada saat itu, seperti mencatat, atau membatalkannya jika perlu). Juga, perhatikan bahwa kami kehilangan Port Input Use Case, karena sekarang controller hanya menggunakan getData()
metode (yang merupakan port output baru kami). Terlebih lagi, bagi saya tampaknya kita melanggar prinsip "katakan, jangan tanya" di sini, karena kami meminta beberapa data kepada interaktifer untuk melakukan sesuatu dengannya, daripada menyuruhnya melakukan hal yang sebenarnya dalam tempat pertama.
Ke titik
Jadi, adakah salah satu dari dua alternatif ini interpretasi "benar" dari Port Keluaran Penggunaan Kasus sesuai dengan Arsitektur Bersih? Apakah keduanya layak?
Jawaban:
Itu tentu tidak Bersih , Bawang , atau Arsitektur Heksagonal . Itu adalah ini :
Bukan berarti MVC harus dilakukan dengan cara itu
Anda dapat menggunakan berbagai cara untuk berkomunikasi antar modul dan menyebutnya MVC . Memberitahu saya sesuatu menggunakan MVC tidak benar-benar memberi tahu saya bagaimana komponen berkomunikasi. Itu tidak standar. Semua yang saya katakan adalah bahwa setidaknya ada tiga komponen yang difokuskan pada tiga tanggung jawab mereka.
Beberapa dari cara itu diberi nama berbeda :
Dan setiap dari mereka dapat dibenarkan disebut MVC.
Bagaimanapun, tidak ada yang benar-benar menangkap apa yang arsitektur buzzword (Bersih, Bawang, dan Hex) semua minta Anda lakukan.
Tambahkan struktur data yang dilemparkan (dan balikkan karena suatu alasan) dan Anda mendapatkan :
Satu hal yang harus jelas di sini adalah bahwa model respons tidak berjalan melalui controller.
Jika Anda memiliki mata elang, Anda mungkin telah memperhatikan bahwa hanya arsitektur kata kunci yang sepenuhnya menghindari ketergantungan melingkar . Yang penting, itu berarti dampak dari perubahan kode tidak akan menyebar melalui siklus melalui komponen. Perubahan akan berhenti ketika hits kode yang tidak peduli.
Bertanya-tanya apakah mereka membalikkannya sehingga aliran kontrol akan melewati searah jarum jam. Lebih lanjut tentang itu, dan ini "putih" kepala panah, nanti.
Karena komunikasi dari Controller ke Presenter dimaksudkan untuk melalui aplikasi "layer" maka ya membuat Controller melakukan bagian dari pekerjaan Presenter kemungkinan bocor. Ini adalah kritik utama saya terhadap arsitektur VIPER .
Mengapa memisahkan ini sangat penting mungkin bisa paling baik dipahami dengan mempelajari Segregasi Tanggung Jawab Kueri Perintah .
Ini adalah API yang Anda kirimi keluaran, untuk kasus penggunaan khusus ini. Tidak lebih dari itu. Interaksi untuk kasus penggunaan ini tidak perlu tahu, atau ingin tahu, jika output akan ke GUI, CLI, log, atau speaker audio. Semua yang perlu diketahui oleh interaktor adalah API yang sangat sederhana yang memungkinkannya melaporkan hasil kerjanya.
Alasan port keluaran berbeda dari port input adalah port itu tidak harus dimiliki oleh lapisan yang diabstraksi. Yaitu, layer yang diabstraksi tidak boleh diizinkan untuk mendikte perubahan padanya. Hanya layer aplikasi dan pembuatnya yang harus memutuskan bahwa port output dapat berubah.
Ini berbeda dengan port input yang dimiliki oleh lapisan itu abstrak. Hanya pembuat lapisan aplikasi yang harus memutuskan apakah port inputnya harus berubah.
Mengikuti aturan-aturan ini mempertahankan gagasan bahwa lapisan aplikasi, atau lapisan dalam apa pun, tidak tahu apa-apa tentang lapisan luar.
Hal penting tentang panah "putih" itu adalah Anda dapat melakukan ini:
Anda bisa membiarkan aliran kontrol berlawanan arah dengan ketergantungan! Itu berarti lapisan dalam tidak perlu tahu tentang lapisan luar, namun Anda bisa menyelam ke lapisan dalam dan kembali keluar!
Melakukan itu tidak ada hubungannya dengan menggunakan kata kunci "antarmuka". Anda bisa melakukan ini dengan kelas abstrak. Heck Anda bisa melakukannya dengan kelas beton (ick) asalkan bisa diperpanjang. Sangat menyenangkan melakukannya dengan sesuatu yang hanya berfokus pada mendefinisikan API yang harus diimplementasikan oleh Presenter. Panah terbuka hanya meminta polimorfisme. Apa yang terserah Anda.
Mengapa membalikkan arah ketergantungan itu sangat penting dapat dipelajari dengan mempelajari Prinsip Ketergantungan Inversi . Saya memetakan prinsip itu ke diagram ini di sini .
Tidak itu benar-benar itu. Tujuan dari memastikan lapisan dalam tidak tahu tentang lapisan luar adalah bahwa kita dapat menghapus, mengganti, atau memperbaiki lapisan luar dengan yakin bahwa hal itu tidak akan merusak apapun di lapisan dalam. Apa yang tidak mereka ketahui tidak akan menyakiti mereka. Jika kita dapat melakukan itu, kita dapat mengubah yang paling luar menjadi apa pun yang kita inginkan.
Masalahnya di sini adalah sekarang apa pun yang tahu bagaimana cara meminta data juga harus menjadi hal yang menerima data. Sebelum Controller dapat memanggil Usecase Interactor dengan gembira tidak menyadari akan seperti apa Model Respon, ke mana harus pergi, dan, heh, bagaimana menyajikannya.
Sekali lagi, pelajari Segregasi Command Query Responsibility untuk melihat mengapa itu penting.
Iya nih! Memberitahu, tidak bertanya, akan membantu menjaga objek ini tetap berorientasi daripada prosedural.
Apa pun yang bekerja itu layak. Tetapi saya tidak akan mengatakan bahwa opsi kedua yang Anda sampaikan dengan setia mengikuti Arsitektur Bersih. Mungkin sesuatu yang berhasil. Tapi bukan itu yang diminta Arsitektur Bersih.
sumber
Dalam sebuah diskusi terkait pertanyaan Anda , Paman Bob menjelaskan tujuan penyaji dalam Arsitektur Bersihnya:
Diberikan contoh kode ini:
Paman Bob mengatakan ini:
(PEMBARUAN: 31 Mei 2019)
Mengingat bahwa jawaban Paman Bob, saya pikir tidak masalah banyak apakah kita melakukan opsi # 1 (biarkan interpraktor menggunakan presenter) ...
... atau kami melakukan opsi # 2 (biarkan interaksor mengembalikan respons, membuat presenter di dalam controller, lalu meneruskan respons ke presenter) ...
Secara pribadi, saya lebih suka opsi # 1 karena saya ingin bisa kontrol di dalam
interactor
ketika untuk menampilkan data dan pesan kesalahan, seperti contoh di bawah ini:... Saya ingin dapat melakukan ini
if/else
yang berhubungan dengan presentasi di dalaminteractor
dan bukan di luar interaktor.Jika di sisi lain kita melakukan pilihan # 2, kita harus menyimpan pesan error (s) dalam
response
objek, kembali bahwaresponse
objek dariinteractor
kecontroller
, dan membuatcontroller
parse yangresponse
objek ...Saya tidak suka parsing
response
data untuk kesalahan di dalamcontroller
karena jika kita melakukan itu kita melakukan pekerjaan yang berlebihan --- jika kita mengubah sesuatu diinteractor
, kita juga harus mengubah sesuatu di dalamnyacontroller
.Juga, jika nanti kami memutuskan untuk menggunakan kembali kami
interactor
untuk menyajikan data menggunakan konsol, misalnya, kami harus ingat untuk menyalin-menempelkan semua yang adaif/else
dicontroller
aplikasi konsol kami.Jika kita menggunakan opsi # 1 kita akan memilikinya
if/else
hanya di satu tempat : theinteractor
.Jika Anda menggunakan ASP.NET MVC (atau kerangka kerja MVC serupa lainnya), opsi # 2 adalah cara yang lebih mudah .
Tapi kita masih bisa melakukan opsi # 1 di lingkungan seperti itu. Berikut adalah contoh melakukan opsi # 1 di ASP.NET MVC:
(Perhatikan bahwa kita perlu memiliki
public IActionResult Result
dalam presenter aplikasi ASP.NET MVC kami)(Perhatikan bahwa kita perlu memiliki
public IActionResult Result
dalam presenter aplikasi ASP.NET MVC kami)Jika kami memutuskan untuk membuat aplikasi lain untuk konsol, kami dapat menggunakan kembali di
UseCase
atas dan hanya membuatController
danPresenter
untuk konsol:(Perhatikan bahwa kami TIDAK MEMILIKI
public IActionResult Result
presenter aplikasi konsol kami)sumber
Kasing dapat berisi presenter atau data yang dikembalikan, tergantung pada apa yang diperlukan oleh aliran aplikasi.
Mari kita memahami beberapa istilah sebelum memahami aliran aplikasi yang berbeda:
A Use Case Berisi Data Pengembalian
Dalam kasus biasa, kasus penggunaan hanya mengembalikan objek domain ke lapisan aplikasi yang selanjutnya dapat diproses dalam lapisan aplikasi untuk membuatnya ramah untuk ditampilkan di UI.
Karena controller bertanggung jawab untuk memanggil use case, dalam hal ini juga berisi referensi dari masing-masing presenter untuk melakukan domain untuk melihat pemetaan model sebelum mengirimkannya untuk melihat yang akan dirender.
Berikut ini contoh kode yang disederhanakan:
Presenter Berisi Case Use
Meskipun tidak umum, tetapi ada kemungkinan bahwa use case mungkin perlu memanggil presenter. Dalam hal itu alih-alih memegang referensi konkret presenter, disarankan untuk mempertimbangkan antarmuka (atau kelas abstrak) sebagai titik referensi (yang harus diinisialisasi dalam jangka waktu yang dijalankan melalui injeksi dependensi).
Memiliki domain untuk melihat logika pemetaan model dalam kelas yang terpisah (bukan di dalam controller) juga memutus ketergantungan melingkar antara controller dan use case (ketika referensi ke logika pemetaan diperlukan oleh kelas use case).
Di bawah ini adalah implementasi yang disederhanakan dari aliran kontrol seperti yang diilustrasikan dalam artikel asli, yang menunjukkan bagaimana hal itu dapat dilakukan. Harap dicatat bahwa tidak seperti yang ditunjukkan pada diagram, demi kesederhanaan UseCaseInteractor adalah kelas yang konkret.
sumber
Meskipun saya umumnya setuju dengan jawaban dari @CandiedOrange, saya juga akan melihat manfaat dalam pendekatan di mana interkom hanya mengoreksi data yang kemudian diteruskan oleh controller ke presenter.
Ini misalnya adalah cara sederhana untuk menggunakan ide-ide Arsitektur Bersih (Ketergantungan Aturan) dalam konteks Asp.Net MVC.
Saya telah menulis posting blog untuk menyelami lebih dalam diskusi ini: https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/
sumber
Pendeknya
Ya, keduanya layak selama kedua pendekatan mempertimbangkan Pembalikan Kontrol antara lapisan bisnis dan mekanisme pengiriman. Dengan pendekatan kedua, kami masih dapat memperkenalkan IOC dengan memanfaatkan pengamat, beberapa mediator pola desain lainnya ...
Dengan Clean Architecture-nya , upaya Paman Bob adalah untuk mensintesis sekelompok arsitektur yang dikenal untuk mengungkapkan konsep dan komponen penting bagi kita untuk mematuhi secara luas dengan prinsip-prinsip OOP.
Akan sangat kontraproduktif untuk mempertimbangkan diagram kelas UML-nya (diagram di bawah) sebagai desain Arsitektur Bersih yang unik . Diagram ini bisa diambil untuk contoh konkret ... Namun, karena jauh lebih abstrak daripada representasi arsitektur biasa, ia harus membuat pilihan konkret di antaranya desain port output berinteraksi yang hanya merupakan detail implementasi ...
Dua sen saya
Alasan utama mengapa saya lebih suka mengembalikan
UseCaseResponse
adalah bahwa pendekatan ini membuat kasus penggunaan saya fleksibel , yang memungkinkan baik komposisi antara mereka dan genericity ( generalisasi dan generasi tertentu ). Contoh dasar:Perhatikan bahwa hal ini secara analog lebih dekat dari kasus penggunaan UML termasuk / memperpanjang satu sama lain dan didefinisikan sebagai dapat digunakan kembali pada subjek yang berbeda (entitas).
Tidak yakin untuk memahami apa yang Anda maksud dengan ini, mengapa Anda perlu "mengontrol" kinerja presentasi? Tidakkah Anda mengontrolnya selama Anda tidak mengembalikan respons use case?
Case use dapat mengembalikan dalam kode status tanggapannya untuk memberi tahu lapisan klien apa yang terjadi persis selama operasinya. Kode status respons HTTP sangat cocok untuk menggambarkan status operasi use case ...
sumber