Arsitektur Bersih: Gunakan case yang berisi presenter atau mengembalikan data?

42

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 Presenterantarmuka 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?

swahnee
sumber
3
Posting silang sangat tidak dianjurkan. Jika ini adalah tempat tinggal pertanyaan Anda, maka Anda harus menghapusnya dari Stack Overflow.
Robert Harvey

Jawaban:

48

Arsitektur Bersih menyarankan untuk membiarkan interaksi use case atau panggilan implementasi aktual presenter (yang disuntikkan, mengikuti DIP) untuk menangani respon / tampilan. Namun, saya melihat orang-orang mengimplementasikan arsitektur ini, mengembalikan data keluaran dari interaktor, dan kemudian membiarkan pengontrol (di lapisan adaptor) memutuskan bagaimana menanganinya.

Itu tentu tidak Bersih , Bawang , atau Arsitektur Heksagonal . Itu adalah ini :

masukkan deskripsi gambar di sini

Bukan berarti MVC harus dilakukan dengan cara itu

masukkan deskripsi gambar di sini

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 : masukkan deskripsi gambar di sini

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.

masukkan deskripsi gambar di sini

Tambahkan struktur data yang dilemparkan (dan balikkan karena suatu alasan) dan Anda mendapatkan :

masukkan deskripsi gambar di sini

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.

Apakah solusi kedua bocor tanggung jawab aplikasi keluar dari lapisan aplikasi, selain tidak secara jelas mendefinisikan port input dan output ke interaktor?

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 .

Port input dan output

Mempertimbangkan definisi Arsitektur Bersih, dan terutama diagram alir kecil yang menjelaskan hubungan antara pengontrol, use case berinteraksi, dan presenter, saya tidak yakin apakah saya benar memahami apa yang seharusnya "Use Case Output Port" seharusnya.

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.

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.

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.


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).

Hal penting tentang panah "putih" itu adalah Anda dapat melakukan ini:

masukkan deskripsi gambar di sini

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 .

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 antarmuka Presenter adalah untuk menjadi cukup abstrak untuk mewakili beberapa jenis presenter (GUI, Web, CLI, dll.), Dan itu benar-benar hanya berarti "output", yang merupakan sesuatu kasus penggunaan mungkin sangat baik, tapi tetap saja saya tidak sepenuhnya percaya diri dengan itu.

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.

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.

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.

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 metode getData () (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.

Iya nih! Memberitahu, tidak bertanya, akan membantu menjaga objek ini tetap berorientasi daripada prosedural.

Ke titik

Jadi, adakah salah satu dari dua alternatif ini interpretasi "benar" dari Port Keluaran Penggunaan Kasus sesuai dengan Arsitektur Bersih? Apakah keduanya layak?

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.

candied_orange
sumber
4
Terima kasih telah meluangkan waktu untuk menulis penjelasan mendalam seperti itu.
swahnee
1
Saya telah mencoba untuk membungkus kepala saya di Arsitektur Bersih, dan jawaban ini telah menjadi sumber yang fantastis. Bagus sekali!
Nathan
Jawaban yang bagus dan terperinci .. Terima kasih untuk itu .. Bisakah Anda memberi saya beberapa tips (atau arahkan ke penjelasan) tentang memperbarui GUI selama UseCase dijalankan, yaitu pembaruan progress bar saat mengunggah file besar?
Ewoks
1
@Ewoks, sebagai jawaban cepat untuk pertanyaan Anda, Anda harus melihat ke dalam pola Yang Dapat Diamati. Kasing penggunaan Anda dapat mengembalikan Subjek dan Memberitahukan Subyek pembaruan kemajuan. Presenter akan Berlangganan Subjek dan menanggapi Pemberitahuan.
Nathan
7

Dalam sebuah diskusi terkait pertanyaan Anda , Paman Bob menjelaskan tujuan penyaji dalam Arsitektur Bersihnya:

Diberikan contoh kode ini:

namespace Some\Controller;

class UserController extends Controller {
    public function registerAction() {
        // Build the Request object
        $request = new RegisterRequest();
        $request->name = $this->getRequest()->get('username');
        $request->pass = $this->getRequest()->get('password');

        // Build the Interactor
        $usecase = new RegisterUser();

        // Execute the Interactors method and retrieve the response
        $response = $usecase->register($request);

        // Pass the result to the view
        $this->render(
            '/user/registration/template.html.twig', 
            array('id' =>  $response->getId()
        );
    }
}

Paman Bob mengatakan ini:

" Tujuan presenter adalah untuk memisahkan kasus penggunaan dari format UI. Dalam contoh Anda, variabel $ response dibuat oleh interaksi, tetapi digunakan oleh tampilan. Ini memasangkan interaksi dengan tampilan. Sebagai contoh , katakanlah bahwa salah satu bidang dalam objek $ response adalah tanggal. Bidang itu akan menjadi objek tanggal biner yang dapat dirender dalam banyak format tanggal yang berbeda. Yang menginginkan format tanggal yang sangat spesifik, mungkin DD / MM / YYYY. Tanggung jawab siapakah yang menciptakan format? Jika berinteraksi menciptakan format itu, maka ia tahu terlalu banyak tentang Tampilan. Tetapi jika tampilan mengambil objek tanggal biner maka ia tahu terlalu banyak tentang interaksinya.

"Tugas presenter adalah mengambil data dari objek respons dan format untuk Tampilan. Baik tampilan maupun interaksi tidak tahu tentang format masing-masing. "

--- Paman Bob

(PEMBARUAN: 31 Mei 2019)

Mengingat bahwa jawaban Paman Bob, saya pikir tidak masalah banyak apakah kita melakukan opsi # 1 (biarkan interpraktor menggunakan presenter) ...

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... atau kami melakukan opsi # 2 (biarkan interaksor mengembalikan respons, membuat presenter di dalam controller, lalu meneruskan respons ke presenter) ...

class Controller
{
    public void ExecuteUseCase(Data data)
    {
        Request request = ...
        UseCase useCase = new UseCase(repository);
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        presenter.Show(response);
    }
}

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:

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... Saya ingin dapat melakukan ini if/elseyang berhubungan dengan presentasi di dalam interactordan bukan di luar interaktor.

Jika di sisi lain kita melakukan pilihan # 2, kita harus menyimpan pesan error (s) dalam responseobjek, kembali bahwa responseobjek dari interactorke controller, dan membuat controller parse yang responseobjek ...

class UseCase
{
    public Response Execute(Request request)
    {
        Response response = new Response();
        if (<invalid request>) 
        {
            response.AddError("...");
        }

        if (<there is another error>) 
        {
            response.AddError("another error...");
        }

        if (response.HasNoErrors)
        {
            response.Whatever = ...
        }

        ...
        return response;
    }
}
class Controller
{
    private UseCase useCase;

    public Controller(UseCase useCase)
    {
        this.useCase = useCase;
    }

    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        if (response.ErrorMessages.Count > 0)
        {
            if (response.ErrorMessages.Contains(<invalid request>))
            {
                presenter.ShowError("...");
            }
            else if (response.ErrorMessages.Contains("another error")
            {
                presenter.ShowError("another error...");
            }
        }
        else
        {
            presenter.Show(response);
        }
    }
}

Saya tidak suka parsing responsedata untuk kesalahan di dalam controllerkarena jika kita melakukan itu kita melakukan pekerjaan yang berlebihan --- jika kita mengubah sesuatu di interactor, kita juga harus mengubah sesuatu di dalamnya controller.

Juga, jika nanti kami memutuskan untuk menggunakan kembali kami interactoruntuk menyajikan data menggunakan konsol, misalnya, kami harus ingat untuk menyalin-menempelkan semua yang ada if/elsedi controlleraplikasi konsol kami.

// in the controller for our console app
if (response.ErrorMessages.Count > 0)
{
    if (response.ErrorMessages.Contains(<invalid request>))
    {
        presenterForConsole.ShowError("...");
    }
    else if (response.ErrorMessages.Contains("another error")
    {
        presenterForConsole.ShowError("another error...");
    }
}
else
{
    presenterForConsole.Present(response);
}

Jika kita menggunakan opsi # 1 kita akan memilikinya if/else hanya di satu tempat : the interactor.


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 Resultdalam presenter aplikasi ASP.NET MVC kami)

class UseCase
{
    private Repository repository;

    public UseCase(Repository repository)
    {
        this.repository = repository;
    }

    public void Execute(Request request, Presenter presenter)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {
            ...
        }
        this.presenter.Show(response);
    }
}
// controller for ASP.NET app

class AspNetController
{
    private UseCase useCase;

    public AspNetController(UseCase useCase)
    {
        this.useCase = useCase;
    }

    [HttpPost("dosomething")]
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new AspNetPresenter();
        useCase.Execute(request, presenter);
        return presenter.Result;
    }
}
// presenter for ASP.NET app

public class AspNetPresenter
{
    public IActionResult Result { get; private set; }

    public AspNetPresenter(...)
    {
    }

    public async void Show(Response response)
    {
        Result = new OkObjectResult(new { });
    }

    public void ShowError(string errorMessage)
    {
        Result = new BadRequestObjectResult(errorMessage);
    }
}

(Perhatikan bahwa kita perlu memiliki public IActionResult Resultdalam presenter aplikasi ASP.NET MVC kami)

Jika kami memutuskan untuk membuat aplikasi lain untuk konsol, kami dapat menggunakan kembali di UseCaseatas dan hanya membuat Controllerdan Presenteruntuk konsol:

// controller for console app

class ConsoleController
{    
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new ConsolePresenter();
        useCase.Execute(request, presenter);
    }
}
// presenter for console app

public class ConsolePresenter
{
    public ConsolePresenter(...)
    {
    }

    public async void Show(Response response)
    {
        // write response to console
    }

    public void ShowError(string errorMessage)
    {
        Console.WriteLine("Error: " + errorMessage);
    }
}

(Perhatikan bahwa kami TIDAK MEMILIKI public IActionResult Resultpresenter aplikasi konsol kami)

Jboy Flaga
sumber
Terima kasih atas kontribusinya. Namun, membaca percakapan itu, ada satu hal yang tidak saya mengerti: dia mengatakan bahwa presenter harus membuat data yang berasal dari respons, dan pada saat yang sama respons itu tidak boleh dibuat oleh interaksor. Tapi siapa yang menciptakan respons? Saya akan mengatakan berinteraksi harus memberikan data kepada presenter, dalam format spesifik aplikasi, yang diketahui oleh presenter, karena lapisan adapter dapat bergantung pada lapisan aplikasi (tetapi tidak sebaliknya).
swahnee
Maafkan saya. Mungkin itu membingungkan karena saya tidak memasukkan contoh kode dari diskusi. Saya akan memperbaruinya untuk memasukkan contoh kode.
Jboy Flaga
Paman Bob tidak mengatakan bahwa tanggapannya tidak boleh dibuat oleh si interaksor. Respons akan dibuat oleh interaksi . Apa yang Paman Bob katakan adalah bahwa respons yang dibuat oleh interaksi akan digunakan oleh presenter. Presenter kemudian akan "memformatnya", meletakkan respons yang diformat ke model tampilan, lalu meneruskan model tampilan tersebut ke tampilan. <br/> Begitulah cara saya memahaminya.
Jboy Flaga
1
Itu lebih masuk akal. Saya mendapat kesan bahwa "view" adalah sinonim untuk "presenter", karena Clean Architecture tidak menyebutkan "view" atau "viewmodel", yang saya percaya hanya konsep MVC, yang mungkin atau mungkin tidak digunakan ketika mengimplementasikan suatu adaptor.
swahnee
2

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:

  • Objek Domain : Objek domain adalah wadah data dalam lapisan domain tempat operasi logika bisnis dijalankan.
  • Lihat Model : Objek Domain biasanya dipetakan untuk melihat model dalam lapisan aplikasi untuk membuatnya kompatibel dan ramah terhadap antarmuka pengguna.
  • Presenter : Walaupun pengontrol di lapisan aplikasi biasanya menggunakan use case, tetapi disarankan untuk mendelegasikan domain untuk melihat logika pemetaan model untuk memisahkan kelas (mengikuti Prinsip Tanggung Jawab Tunggal), yang disebut "Presenter".

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:

namespace SimpleCleanArchitecture
{
    public class OutputDTO
    {
        //fields
    }

    public class Presenter 
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    public class Domain
    {
        //fields
    }

    public class UseCaseInteractor
    {
        public Domain Process(Domain domain)
        {
            // additional processing takes place here
            return domain;
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            UseCaseInteractor userCase = new UseCaseInteractor();
            var domain = userCase.Process(new Domain());//passing dummy domain(for demonstration purpose) to process
            var presenter = new Presenter();//presenter might be initiated via dependency injection.

            return new View(presenter.Present(domain));
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

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).

masukkan deskripsi gambar di sini

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.

namespace CleanArchitectureWithPresenterInUseCase
{
    public class Domain
    {
        //fields
    }

    public class OutputDTO
    {
        //fields
    }

    // Use Case Output Port
    public interface IPresenter
    {
        OutputDTO Present(Domain domain);
    }

    public class Presenter: IPresenter
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    // Use Case Input Port / Interactor   
    public class UseCaseInteractor
    {
        IPresenter _presenter;
        public UseCaseInteractor (IPresenter presenter)
        {
            _presenter = presenter;
        }

        public OutputDTO Process(Domain domain)
        {
            return _presenter.Present(domain);
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            IPresenter presenter = new Presenter();//presenter might be initiated via dependency injection.
            UseCaseInteractor userCase = new UseCaseInteractor(presenter);
            var outputDTO = userCase.Process(new Domain());//passing dummy domain (for demonstration purpose) to process
            return new View(outputDTO);
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}
Ashraf
sumber
1

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/

polosionis
sumber
1

Gunakan case yang berisi presenter atau mengembalikan data?

Jadi, adakah salah satu dari dua alternatif ini interpretasi "benar" dari Port Keluaran Penggunaan Kasus sesuai dengan Arsitektur Bersih? Apakah keduanya layak?


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 ...

Diagram kelas UML tentang Arsitektur Bersih Paman Bob


Dua sen saya

Alasan utama mengapa saya lebih suka mengembalikan UseCaseResponseadalah bahwa pendekatan ini membuat kasus penggunaan saya fleksibel , yang memungkinkan baik komposisi antara mereka dan genericity ( generalisasi dan generasi tertentu ). Contoh dasar:

// A generic "entity type agnostic" use case encapsulating the interaction logic itself.
class UpdateUseCase implements UpdateUseCaseInterface
{
    function __construct(EntityGatewayInterface $entityGateway, GetUseCaseInterface $getUseCase)
    {
        $this->entityGateway = $entityGateway;
        $this->getUseCase = $getUseCase;
    }

    public function execute(UpdateUseCaseRequestInterface $request) : UpdateUseCaseResponseInterface
    {
        $getUseCaseResponse = $this->getUseCase->execute($request);

        // Update the entity and build the response...

        return $response;
    }
}

// "entity type aware" use cases encapsulating the interaction logic WITH the specific entity type.
final class UpdatePostUseCase extends UpdateUseCase;
final class UpdateProductUseCase extends UpdateUseCase;

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).


Pada interaktor mengembalikan data

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).

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 ...

ClemC
sumber