Temiz Mimari: Sunucuyu içeren veya verileri döndüren bir durum mu kullanın?


42

Temiz Mimarlık tepkisi / görüntü işlemek için (DIP takiben enjekte edilir) sunum gerçek uygulanmasını diyoruz interaktörüdür bir kullanım durumunda izin önerir. Ancak, bu mimariyi uygulayan, çıktı verilerini etkileşimden döndüren ve ardından denetleyicinin (bağdaştırıcı katmanındaki) nasıl işleyeceğine karar vermesine izin veren insanlar görüyorum. İkinci çözüm, uygulama sorumluluklarını uygulama katmanından dışarı atıyor mu, ayrıca etkileşimciye giriş ve çıkış portlarını net bir şekilde tanımlamamış mı?

Giriş ve çıkış portları

Temiz Mimari tanımını ve özellikle bir denetleyici, bir kullanım senaryosu etkileşimcisi ve sunum yapan arasındaki ilişkileri tanımlayan küçük akış şeması göz önüne alındığında , "Kullanım Durumu Çıktı Portunun" ne olması gerektiğini doğru bir şekilde anladığımdan emin değilim.

Temiz mimari, altıgen mimari gibi, birincil portlar (yöntemler) ve ikincil portlar (adaptörler tarafından uygulanacak arayüzler) arasında ayrım yapar. İletişim akışını takiben "Kullanım Durumu Giriş Bağlantı Noktası" nın birincil bir bağlantı noktası olmasını (bu nedenle sadece bir yöntem) ve "Kullanım Durumu Çıkış Bağlantı Noktasını" uygulanacak bir arabirim, belki de gerçek bağdaştırıcıyı alan bir yapıcı argümanı olmasını bekliyorum. Böylece etkileşici onu kullanabilir.

Kod örneği

Bir kod örneği yapmak için, bu denetleyici kodu olabilir:

Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();

Sunucu arayüzü:

// Use Case Output Port
interface Presenter
{
    public void present(Data data);
}

Son olarak, etkileşimin kendisi:

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);
    }
}

Sunucuyu çağıran etkileşimde

Önceki yorumlama, yukarıda belirtilen diyagramın kendisi tarafından onaylanmış gibi görünmektedir, burada denetleyici ve giriş portu arasındaki ilişki, "keskin" bir kafa ("ilişkilendirme için UML" anlamına gelen "keskin" bir kafaya sahip olan katı bir okla temsil edilir; denetleyicinin "kullanım durumu" varken, sunucu ile çıktı portu arasındaki ilişki "beyaz" kafalı ("devralma" için UML, "uygulama" için değil, muhtemelen Zaten anlamı budur).

Ayrıca, başka bir soruya verilen bu cevapta , Robert Martin, tam olarak konuşmacının sunumcuyu okuma isteği üzerine çağırdığı bir kullanım durumunu açıklar:

Haritaya tıklamak, placePinController'ın çağrılmasını sağlar. Tıklamanın konumunu ve diğer tüm bağlamsal verileri toplar, bir placePinRequest veri yapısı oluşturur ve onu, iğnenin konumunu denetleyen, gerekirse doğrulayan, iğneyi kaydetmek için bir Yer varlığı oluşturan, bir EditPlaceReponse oluşturan bir Place öğesi oluşturan PlacePinInteractor'a iletir nesne ve yer editörü ekranını getiren EditPlacePresenter'a iletir.

Bunun MVC ile iyi bir şekilde oynamasını sağlamak için, geleneksel olarak kontrol cihazına girecek olan uygulama mantığının burada etkileşime girdiğini düşünüyorum, çünkü herhangi bir uygulama mantığının uygulama katmanının dışına sızmasını istemiyoruz. Bağdaştırıcılar katmanındaki denetleyici etkileşimi yalnızca çağırır ve işlem sırasında küçük bir veri biçimi dönüştürmesi yapabilir:

Bu katmandaki yazılım, verileri kullanım durumları ve varlıklar için en uygun formattan, Veri tabanı veya Web gibi bazı harici ajanslar için en uygun formata dönüştüren bir adaptörler setidir.

Orijinal makaleden Arayüz Adaptörleri hakkında konuşuyor.

Verileri dönen etkileşimli

Bununla birlikte, bu yaklaşımla ilgili sorunum, kullanım durumunun sunumun kendisiyle ilgilenmesi gerektiğidir. Şimdi, Presenterarabirimin amacının birkaç farklı türdeki sunucuyu (GUI, Web, CLI, vb.) Temsil edecek kadar soyut olduğunu ve bunun gerçekten bir kullanım durumu olabilecek "çıktı" anlamına geldiğini görüyorum . çok iyi, ama yine de tamamen kendime güvenmiyorum.

Şimdi, temiz mimarinin uygulamaları için Web'in etrafına bakarken, yalnızca çıkış portunu bazı DTO'ları döndüren bir yöntem olarak yorumlayan insanları buluyorum. Bu gibi bir şey olurdu:

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

Bu caziptir çünkü sunumu "kullanım" durumunun dışına "çağırmak" sorumluluğunu taşıyoruz, bu nedenle kullanım durumu, yalnızca verileri sağlamak yerine verilerle ne yapılacağını bilmekle ilgilenmiyor. Ayrıca, bu durumda hala bağımlılık kuralını çiğnemiyoruz, çünkü kullanım durumu hala dış katman hakkında hiçbir şey bilmiyor.

Bununla birlikte, kullanım durumu, gerçek sunumun artık yapıldığı anı kontrol etmemektedir (bu, örneğin o noktada ek şeyler yapmak, kayıt yapmak ya da gerektiğinde tamamen iptal etmek için faydalı olabilir). Ayrıca, Case Case Portunu Kullandığımızı da fark ettiğimize dikkat edin, çünkü kontrolör sadece getData()metodu kullanıyor (ki bu bizim yeni çıkış portumuzdur). Dahası, burada bana "söyleme, sorma" ilkesini çiğniyor olduğumuza bakıyor, çünkü etkileşimciden bazı verilerin, içinde gerçek şeyi yapmasını söylemek yerine onunla bir şeyler yapmasını istiyoruz. ilk yer.

Diyeceğim şey şu ki

Öyleyse, bu iki alternatiften herhangi biri, Clean Case'a Uygun Kullanım Case Çıkışı Portunun "doğru" yorumu mu? İkisi de uygulanabilir mi?


3
Çapraz gönderme kesinlikle önerilmez. Sorunuzun yaşamasını istediğiniz yer burasıysa, Yığın Taşması'ndan silmelisiniz.
Robert Harvey,

Yanıtlar:


48

Temiz Mimari, bir kullanım durumu etkileşiminin, yanıt / görüntüyü işlemek için sunum yapan kişinin (DIP'yi takiben enjekte edilen) gerçek uygulamasını çağırmasını önerir. Ancak, bu mimariyi uygulayan, çıktı verilerini etkileşimden döndüren ve ardından denetleyicinin (bağdaştırıcı katmanındaki) nasıl işleyeceğine karar vermesine izin veren insanlar görüyorum .

Bu kesinlikle Temiz , Soğan veya Altıgen Mimari değildir. İşte bu :

görüntü tanımını buraya girin

Değil o MVC böyle yapılacak olan

görüntü tanımını buraya girin

Modüller arasında iletişim kurmak ve onu MVC olarak adlandırmak için birçok farklı yol kullanabilirsiniz . Bana bir şey kullanan MVC'yi söylemek, bileşenlerin nasıl iletişim kurduğunu gerçekten söylemiyor. Bu standart değil. Bana tek söylediği, üç sorumluluk üzerine yoğunlaşan en az üç bileşen olduğudur.

Bu yollardan bazılarına farklı adlar verildi : görüntü tanımını buraya girin

Ve bunların her birine haklı olarak MVC denilebilir.

Her neyse, hiçbiri buzzword mimarisinin (Clean, Onion ve Hex) yapmanı istediği şeyi yakalayamıyor.

görüntü tanımını buraya girin

Döndürülen veri yapılarını ekleyin (ve nedense baş aşağı çevirin) ve şunları elde edin :

görüntü tanımını buraya girin

Burada açık olması gereken şeylerden biri, yanıt modelinin denetleyiciden geçmeyeceğidir.

Eğer bir kartal gözüyse, sadece sözcük kelimesi mimarilerinin tamamen dairesel bağımlılıklardan kaçındığını fark etmiş olabilirsiniz . Önemli olan, bir kod değişikliğinin etkisinin bileşenler arasında geçiş yaparak yayılmayacağı anlamına gelir. Değişiklik umursamayan bir koda çarptığında duracaktır.

Acaba tersine mi çevirdiler ki kontrol akışı saat yönünde ilerlesin. Bundan daha fazlası ve bu "beyaz" ok başları.

İkinci çözüm, uygulama sorumluluklarını uygulama katmanından dışarı atıyor mu, ayrıca etkileşimciye giriş ve çıkış portlarını net bir şekilde tanımlamamış mı?

Denetleyiciden Sunucuyla iletişimin, "katman" uygulamasından geçmesi gerektiği için, Denetçinin Sunucular işinin bir bölümünü yapması evet bir sızıntı olabilir. Bu benim VIPER mimarisinin temel eleştirisi .

Bunları ayırmanın neden bu kadar önemli olduğu konusunda Komuta Sorgusu Sorumluluk Ayrıştırması çalışılarak muhtemelen en iyi şekilde anlaşılabilir .

Giriş ve çıkış portları

Temiz Mimari tanımını ve özellikle bir denetleyici, bir kullanım durumu etkileşimi yapan kişi ve bir sunumcu arasındaki ilişkileri tanımlayan küçük akış şeması göz önüne alındığında, "Kullanım Durumu Çıktı Bağlantı Noktasını Kullan" ın ne olması gerektiğini doğru bir şekilde anladığımdan emin değilim.

Bu özel kullanım durumu için çıktı gönderdiğiniz API'dir. Bundan daha fazlası değil. Bu kullanım durumu için etkileşime giren, bir GUI'ye, bir CLI'ye, bir günlüke veya bir ses hoparlörüne gidip gelmeyeceğini bilmek veya bilmek istemez. Etkileşimcinin bilmesi gereken tek şey, çalışmasının sonuçlarını rapor etmesine izin verecek en basit API'dir.

Temiz mimari, altıgen mimari gibi, birincil portlar (yöntemler) ve ikincil portlar (adaptörler tarafından uygulanacak arayüzler) arasında ayrım yapar. İletişim akışını takiben "Kullanım Durumu Giriş Bağlantı Noktası" nın birincil bir bağlantı noktası olmasını (bu nedenle sadece bir yöntem) ve "Kullanım Durumu Çıkış Bağlantı Noktasını" uygulanacak bir arabirim, belki de gerçek bağdaştırıcıyı alan bir yapıcı argümanı olmasını bekliyorum. Böylece etkileşici onu kullanabilir.

Çıkış portunun giriş portundan farklı olmasının nedeni, soyutladığı katman tarafından KENDİ OLMAMALIDIR. Yani, soyutladığı katmanın ona yapılan değişiklikleri dikte etmesine izin verilmemelidir. Sadece uygulama katmanı ve yazarı çıkış portunun değişebileceğine karar vermelidir.

Bu, soyutladığı katmanın sahip olduğu giriş portunun aksinedir. Yalnızca uygulama katmanı yazarı, giriş portunun değişip değişmemesi gerektiğine karar vermelidir.

Bu kurallara uyarak, uygulama katmanının veya herhangi bir iç katmanın, dış katmanlar hakkında hiçbir şey bilmediği fikrini korur.


Sunucuyu çağıran etkileşimde

Önceki yorumlama, yukarıda belirtilen diyagramın kendisi tarafından onaylanmış gibi görünmektedir, burada denetleyici ve giriş portu arasındaki ilişki, "keskin" bir kafa ("ilişkilendirme için UML" anlamına gelen "keskin" bir kafaya sahip olan katı bir okla temsil edilir; denetleyicinin "kullanım durumu" varken, sunucu ile çıktı portu arasındaki ilişki "beyaz" kafalı ("devralma" için UML, "uygulama" için değil, muhtemelen Zaten anlamı budur).

Bu "beyaz" okla ilgili en önemli şey, bunu yapmanıza izin vermesidir:

görüntü tanımını buraya girin

Kontrol akışının bağımlılığın tersi yönde gitmesine izin verebilirsiniz! Bu, iç katman dış katman hakkında bir şey bilmek zorunda değil, ancak iç katmana dalabilir ve geri dönebilirsiniz!

Bunu yapmak "interface" anahtar sözcüğünü kullanmakla hiçbir ilgisi yoktur. Bunu soyut bir sınıfla yapabilirsin. Heck, uzatılabildiği sürece (ick) beton sınıfı ile yapabilirsin. Sadece Presenter'ın uygulaması gereken API'yi tanımlamaya odaklanan bir şeyle yapmak güzel. Açık ok sadece polimorfizm istiyor. Ne çeşit sana kalmış?

Neden bu bağımlılığın yönünü tersine çevirmenin önemli olduğu Bağımlılık İnversiyon Prensibi'ni inceleyerek öğrenilebilir . Bu prensibi burada bu diyagramlara eşledim .

Verileri dönen etkileşimli

Bununla birlikte, bu yaklaşımla ilgili sorunum, kullanım durumunun sunumun kendisiyle ilgilenmesi gerektiğidir. Şimdi, Presenter arayüzünün amacının birkaç farklı türde sunumcuyu (GUI, Web, CLI, vb.) Temsil edecek kadar soyut olduğunu ve bunun gerçekten bir kullanım durumu olan "çıktı" anlamına geldiğini görüyorum. çok iyi olabilir, ama yine de tamamen kendime güvenmiyorum.

Hayır bu gerçekten. İç katmanların dış katmanları bilmediğinden emin olmamızın amacı, dış katmanları söküp, değiştirebileceğimiz veya yeniden düzenleyebileceğimiz, böylece iç katmanlarda herhangi bir şey kırmayacağımızdır. Bilmedikleri şey onlara zarar vermez. Bunu yapabilirsek, dış olanları istediğimiz şekilde değiştirebiliriz.

Şimdi, temiz mimarinin uygulamaları için Web'in etrafına bakarken, yalnızca çıkış portunu bazı DTO'ları döndüren bir yöntem olarak yorumlayan insanları buluyorum. Bu gibi bir şey olurdu:

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

Bu caziptir çünkü sunumu "kullanım" durumunun dışına "çağırmak" sorumluluğunu taşıyoruz, bu nedenle kullanım durumu, yalnızca verileri sağlamak yerine verilerle ne yapılacağını bilmekle ilgilenmiyor. Ayrıca, bu durumda hala bağımlılık kuralını çiğnemiyoruz, çünkü kullanım durumu hala dış katman hakkında hiçbir şey bilmiyor.

Buradaki sorun şimdi, veri için nasıl sorulacağını bilen her şey, aynı zamanda verileri kabul eden şey olmak zorunda. Kontrolör, Usecase Interactor'ı arayabilmeden önce, Müdahale Modelinin neye benzeyeceğini, nereye gideceğini ve nasıl sunacağını bilmiyor.

Yine, bunun neden önemli olduğunu görmek için lütfen Komut Sorgu Sorumluluk Ayrımı bölümünü inceleyin.

Bununla birlikte, kullanım durumu, gerçek sunumun artık yapıldığı anı kontrol etmemektedir (bu, örneğin o noktada ek şeyler yapmak, kayıt yapmak ya da gerektiğinde tamamen iptal etmek için faydalı olabilir). Ayrıca, Vaka Girişi Portunu Kullandığımızı da fark ettiğimize dikkat edin, çünkü kontrolör sadece getData () metodunu kullanıyor (ki bu bizim yeni çıkış portumuzdur). Dahası, burada bana "söyleme, sorma" ilkesini çiğniyor olduğumuza bakıyor, çünkü etkileşimciden bazı verilerin, içinde gerçek şeyi yapmasını söylemek yerine onunla bir şeyler yapmasını istiyoruz. ilk yer.

Evet! Söylememek, söylememek, bu nesneyi işlemden ziyade yönlendirmeye yardımcı olacaktır.

Diyeceğim şey şu ki

Öyleyse, bu iki alternatiften herhangi biri, Clean Case'a Uygun Kullanım Case Çıkışı Portunun "doğru" yorumu mu? İkisi de uygulanabilir mi?

Çalışan her şey uygulanabilir. Ancak, sadık sunacağınız ikinci seçeneğin Temiz Mimariyi takip ettiğini söyleyemem. İşe yarayan bir şey olabilir. Ancak, Clean Architecture'ın istediği şey bu değil.


4
Böyle derinlemesine bir açıklama yazmak için zaman ayırdığınız için teşekkür ederiz.
swahnee

1
Kafamı Temiz Mimari etrafına sarmaya çalışıyorum ve bu cevap harika bir kaynak oldu. Çok iyi yapılmış!
Nathan,

Harika ve ayrıntılı cevap .. Bunun için teşekkürler .. UseCase çalıştırması sırasında GUI'yi güncelleme hakkında bazı ipuçları (veya açıklamalara yer verebilir misiniz?), Yani büyük dosya yüklerken ilerleme çubuğu güncellemesi yapabilir misiniz?
Ewoklar

1
@Ewoks, sorunuza hızlı bir cevap olarak, Gözlenebilir kalıplara bakmalısınız. Kullanım durumunuz bir Konu döndürebilir ve ilerleme durumundaki güncellemeleri Konuya Bildirir. Sunucu konuya abone olacak ve bildirimlere cevap verecektir.
Nathan

7

Sorunuzla ilgili bir tartışmada , Bob Amca, Sunucunun Temiz Mimarisindeki amacını açıklar:

Bu kod örneği verildiğinde:

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()
        );
    }
}

Bob Amca şunu söyledi:

" Sunumcunun amacı, kullanım durumlarını Kullanıcı Arabirimi biçiminden ayırmaktır. Örneğinizde, $ yanıt değişkeni, etkileşimli tarafından oluşturulur, ancak görünüm tarafından kullanılır. Bu, etkileşleyiciyi görünüme bağlar. Örneğin. $ yanıt nesnesindeki alanlardan birinin bir tarih olduğunu varsayalım, bu alan birçok farklı tarih biçiminde işlenebilen ikili bir tarih nesnesi olabilir.Dikey bir tarih biçimi ister, belki de DD / MM / YYYY. Biçimi oluşturmak kimin sorumluluğundadır? Eğer etkileşimci bu biçimi oluşturursa, Görünüm hakkında çok şey bilir. Ancak görünüm ikili tarih nesnesini alırsa, etkileşimci hakkında çok fazla şey bilir.

"Sunumcunun işi almaktır. Yanıt nesnesindeki verileri görüntüleyin ve Görünüm için biçimlendirin. Ne görüş, ne de etkileşimci birbirinin formatlarını bilmiyor. "

--- Bob Amca

(GÜNCELLEME: 31 Mayıs 2019)

Bob Amca’nın verdiği cevabı göz önüne alındığında, seçenek # 1 yapıp yapmamamızın bir önemi olmadığını düşünüyorum (etkileşimli sunucunun sunumcuyu kullanmasına izin verin) ...

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);
    }
}

... ya da # 2 seçeneğimizi yaparız (etkileşimcinin yanıt vermesine izin ver, denetleyicinin içinde bir sunucu oluştur, sonra da sunucuyu yanıtla).

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);
    }
}

Şahsen, opsiyon 1. tercih ben edebilmek kontrolü olmak istiyorum çünkü içerideinteractor aşağıda bu örnekte olduğu gibi, veri ve hata mesajlarını göstermek için:

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);
    }
}

... Ben etkileşimin dışında, if/elseiçinde interactorve dışında sunum ile ilgili olanları yapabilmek istiyorum .

Öte yandan biz seçeneği 2. yaparsanız, biz de hata mesaj (lar) saklamak zorunda kalacak response, nesnenin o dönmek responsenesneyi interactoriçin controllerve yapmak controller Ayrıştırmaresponse nesne ...

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);
        }
    }
}

responseİçindeki hatalar için veri ayrıştırmayı sevmem , controllerçünkü bunu yaparsak fazla çalışma yapıyoruz --- içinde bir şeyi değiştirirsek interactor, içinde de bir şeyi değiştirmemiz gerekir controller.

Daha sonra bizim yeniden kullanmaya karar Ayrıca, interactorörneğin, konsolunu kullanarak mevcut verilere, biz tüm bu kopyala-yapıştır için hatırlamak zorunda if/elseiçinde controllerbizim konsol uygulamasının.

// 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);
}

Eğer # 1 seçeneğini kullanırsak, bunu if/else sadece bir yerde elde ederiz : the interactor.


ASP.NET MVC (veya benzeri bir MVC çerçevesi) kullanıyorsanız, seçenek # 2 gitmek için en kolay yoldur.

Ancak yine de bu tür bir ortamda 1. seçeneği yapabiliriz. ASP.NET MVC'de # 1 seçeneğinin yapılmasına bir örnek:

( public IActionResult ResultASP.NET MVC uygulamamızın sunumunda olması gerektiğine dikkat edin)

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);
    }
}

( public IActionResult ResultASP.NET MVC uygulamamızın sunumunda olması gerektiğine dikkat edin)

Konsol için başka bir uygulama oluşturmaya karar verirsek, UseCaseyukarıdakileri yeniden kullanabilir ve sadece Controllerve Presenterkonsol için oluşturabiliriz :

// 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);
    }
}

( public IActionResult ResultKonsol uygulamamızın sunucusunda bulunmadığına dikkat edin)


Katkı için teşekkürler. Ancak sohbeti okumak, anlamadığım bir şey var: sunum yapan kişinin yanıttan gelen verileri vermesi gerektiğini ve aynı zamanda yanıtın etkileşimli tarafından yaratılmaması gerektiğini söylüyor. Peki o zaman yanıtı kim yaratıyor? Etkileşimcinin verileri sunucuya, uygulamaya özgü formatta, bu da sunucu tarafından bilinen, veriyi sağlaması gerektiğini söyleyebilirim, çünkü adaptörler katmanı uygulama katmanına bağlı olabilir (ancak tersi değil).
swahnee

Üzgünüm. Belki kafa karıştırıcı olur çünkü tartışmadaki kod örneğini dahil etmedim. Kod örneğini eklemek için güncelleyeceğim.
Jboy Flaga 15:17

Bob Amca, cevabın etkileşimciler tarafından yaratılmaması gerektiğini söylemedi. Cevap, etkileşimli tarafından oluşturulacak . Bob Amca'nın söylediği, konuşmacı tarafından yaratılan yanıtın sunum yapan tarafından kullanılacağıdır. Sunucu daha sonra "biçimlendirir", biçimlendirilmiş yanıtı bir görünüm modeline koyar, sonra bu görünüm modelini görünüme geçirir. <br/> Bu şekilde anlıyorum.
Jboy Flaga

1
Bu daha mantıklı. "Mimari" nin "sunucu" ile eşanlamlı olduğu izlenimindeydim, çünkü Temiz Mimari, yalnızca uygularken kullanılabilecek veya kullanmayacağına inandığım MVC kavramları olduğuna inandığım "görünüm" veya "görünüm modelinden" bahsetmiyor. adaptörü.
swahnee

2

Bir kullanım durumu, uygulama akışının neye ihtiyaç duyduğuna bağlı olarak, sunum yapan kişiyi veya geri dönen verileri içerebilir.

Farklı uygulama akışlarını anlamadan önce birkaç terimi anlayalım:

  • Etki Alanı Nesnesi : Bir etki alanı nesnesi, iş mantığı işlemlerinin taşındığı etki alanı katmanındaki veri kabıdır.
  • Model Görüntüle : Etki Alanı Nesneleri, genellikle uygulama katmanındaki modelleri görüntülemek için onları uyumlu ve kullanıcı arabirimiyle uyumlu hale getirmek için eşlenir.
  • Sunum Yapan : Uygulama katmanındaki bir denetleyici tipik olarak bir kullanım durumu başlatır, ancak etki alanı için model eşleme mantığını görüntülemek üzere, “Sunum Yapıcı” adı verilen ayrı bir sınıfa (Tek Sorumluluk İlkesi'ni takiben) ayırmak önerilir.

Dönen Verileri İçeren Bir Kullanım Örneği

Genel bir durumda, bir kullanım durumu, kullanıcı arayüzünde gösterilmesini kolaylaştıracak şekilde uygulama katmanında işlenebilecek bir uygulama nesnesini uygulama katmanına döndürür.

Kontrolör kullanım durumunun çağrılmasından sorumlu olduğu için, bu durumda ayrıca, oluşturulacak görünüme göndermeden önce model haritalamasını görüntülemek üzere etki alanı yapmak için ilgili sunum yapan kişinin referansını da içerir.

İşte basitleştirilmiş bir kod örneği:

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;
        }

    }
}

Sunucuyu İçeren Bir Kullanım Örneği

Yaygın olmasa da, kullanım durumunun sunumcuyu çağırması gerekebilir. Bu durumda, sunucunun somut referansını tutmak yerine, bir arayüz (veya soyut sınıfı) referans noktası olarak düşünmeniz önerilir (bağımlılık enjeksiyonuyla çalışma zamanında başlatılmalıdır).

Model eşleme mantığını ayrı bir sınıfta (kontrol ünitesinin yerine) görüntülemek için etki alanına sahip olmak aynı zamanda kontrol cihazı ve kullanım senaryosu arasındaki dairesel bağımlılığı da kırar (kullanım mantığı sınıfına göre eşleştirme mantığına referans gerektiğinde).

görüntü tanımını buraya girin

Aşağıda, nasıl yapılabileceğini gösteren orijinal makalede gösterildiği gibi kontrol akışının basitleştirilmiş bir uygulaması verilmiştir. Şemada gösterilenden farklı olarak, kullanım kolaylığı için UseCaseInteractor'ın somut bir sınıf olduğunu lütfen unutmayın .

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;
        }

    }
}


1

Sunucuyu içeren veya verileri döndüren bir durum mu kullanın?

Öyleyse, bu iki alternatiften herhangi biri, Clean Case'a Uygun Kullanım Case Çıkışı Portunun "doğru" yorumu mu? İkisi de uygulanabilir mi?


Kısacası

Evet, her iki yaklaşım da iş katmanı ve teslimat mekanizması arasındaki Denetimi Tersine Çevirme'yi dikkate aldığı sürece her ikisi de uygulanabilir . İkinci yaklaşımla, gözlemciyi, arabulucu az sayıdaki diğer tasarım desenlerini kullanarak IOC'yi tanıtabiliyoruz ...

Temiz Mimarisi ile Bob Amca'nın girişimi, OOP prensiplerine geniş bir şekilde uymamız için önemli kavramları ve bileşenleri ortaya çıkarmak için bilinen bir dizi mimariyi sentezlemektir.

UML sınıf diyagramını (aşağıdaki diyagram) benzersiz Temiz Mimari tasarımı olarak düşünmek, karşı verim kazanır . Bu şema somut örnekler uğruna çizilebilirdi … Ancak, olağan mimarlık temsillerinden çok daha az soyut olduğu için aralarında sadece uygulama detayı olan etkileşimli çıkış portu tasarımı olan somut seçimler yapmak zorunda kaldı …

Bob Amca'nın Temiz Mimarinin UML sınıf diyagramı


Benim iki Sentim

Geri dönmeyi tercih etmemin temel nedeni UseCaseResponse, bu yaklaşımın kullanım durumlarımı esnek tutması , aralarındaki kompozisyonun ve jenerikliğin ( genelleme ve özel üretim ) sağlanması. Temel bir örnek:

// 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;

UML'nin birbirlerini içeren / genişleten ve farklı konularda (varlıklar) tekrar kullanılabilir olarak tanımlanan vakaları benzer şekilde yaklaştığını unutmayın .


Verileri dönen etkileşimli

Bununla birlikte, kullanım durumu, gerçek sunumun artık yapıldığı anı kontrol etmemektedir (bu, örneğin o noktada ek şeyler yapmak, kayıt yapmak ya da gerektiğinde tamamen iptal etmek için faydalı olabilir).

Bununla ne demek istediğinizi anladığınızdan emin değilsiniz, neden sunum performasyonunu "kontrol etmeniz" gerekiyor? Kullanım davası yanıtını iade etmediğiniz sürece kontrol etmiyor musunuz?

Kullanım durumu, tam olarak çalışması sırasında olanları müşteri katmanına bildirmek için yanıtında bir durum kodu verebilir. HTTP yanıt durumu kodları, bir kullanım durumunun çalışma durumunu açıklamak için özellikle uygundur ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.