2016-06-09 15 views
4

Ich versuche, einen neuen architektonischen Ansatz für die Kommunikation zwischen den Schichten zu implementieren, indem ich abstrakte Modelle und Dekoratoren verwende.Dekoratoren für Inter-Layer-Kommunikation

Wenn wir die Schichten einer monolithischen Anwendung entwerfen, hätten wir normalerweise eine Anwendungsschicht (Controller), eine Domänenschicht (Business) und eine Infrastrukturschicht (Persistenz). Diese Schichten kommunizieren miteinander über eine konkrete Modelldefinition, die beim Lesen aus einer Datenbank durch ein Repository, dann zu einem Service und schließlich zu einem Controller navigiert, der sie an einen Client ausgibt.

Das heißt, lassen Sie uns auf den Punkt ...

Normalerweise muss das Infrastrukturmodell nicht das gleiche wie das Geschäftsmodell, die auch nicht das gleiche wie das UI-Modell sein muss. Das Infrastrukturmodell kann sich nur auf die Struktur des Ziel-Repositorys beziehen, der Domänen-Layer kann sich nur auf den Geschäftsbetrieb beziehen, und der UI-Layer muss nur die Clients und Daten lesen/eingeben.

Alle diese Schichten müssen eine Abstraktion voneinander "verstehen" oder sogar bidirektionale Wertezuordnung (oder DTOs) implementieren, um zwischen ihnen zu kommunizieren. Mapping/DTO ist ein schlechter Ansatz, da wir für die Datenzuordnung unnötige Verarbeitung benötigen.

Also, hier ist, was ich versuche zu tun: Separate Schichten mit Dekoratoren für die Kommunikation.

In diesem Ansatz würden wir abstrakte Komponenten und seine Dekoratoren auf einem gemeinsamen Modul haben, und jede Schicht hätte ihren eigenen Dekorator.

Eg .:

  • AbstractFoo ist eine Abstraktion;
  • FooDecorator implementiert AbstractFoo und umschließt auch ein AbstractFoo;
  • FooEntity ist ein Infrastrukturmodell, das FooDecorator implementiert und Ausdauer Sachen
  • FooBusiness ist ein Geschäftsmodell, das FooEntity Dekorateur implementiert Griff und Business-Sachen
  • FooView ist ein Controller-Modell, das FooBusiness Dekorateur implementiert und Griff UI Felder, Codierung behandeln und ... etc

Mit diesem Ansatz konnten wir:

  • haben nur 1 Objekt im Speicher hält Foo Informationen, aber e sehr Schicht "Köpfe, es ist ein eigenes Geschäft".
  • Trennen Sie die Schichten in verschiedenen Modulen, sodass das Unternehmen nur die infra importiert und die Anwendungsschicht nur das Geschäft importiert.
  • Wir könnten Stubs für dumme Logiken verteilen, ohne unsere Domain zu vergeben, wie einen shopping-cart Service, der nur die Zusammenfassung eines Produkts kennen muss, aber nicht das Domain-Objekt selbst.
  • Wir könnten verschiedene Teams haben, die in verschiedenen Schichten der Anwendung ohne Konflikt arbeiten.

Hier ist ein Beispiel auf Papier:

Sketch

Also ... was ich verlangen Vorschlag ist, Tipps und einigen Meinungen, wenn es ein guter Ansatz ist.

[UPDATE # 1]:

meine Frage zu vereinfachen, werde ich einen Anwendungsfall schreiben unter:

gemeinsam genutzter Komponenten:

// Abstract Foo 
public abstract class AbstractFoo { 
    protected Long id; 
    protected String color; 
    protected LocalDateTime lastModified; 

    protected Long getId(); 
    protected String getColor(); 

    protected void setId(); 
    protected void setColor(); 
} 


// Abstract Decorator(wraps foo) 
public abstract class FooDecorator extends AbstractFoo { 
    private final Foo foo; 
    protected FooDecorator(Foo foo) { this.foo = foo; } 

    protected Long getId() {return foo.getId();} 
    protected String getColor() {return foo.getColor();} 
    protected LocalDateTime getLastModified() {return foo.getLastModified();} 

    protected void setId(Long id){foo.setId(id);} 
    protected void setColor(String color){foo.setColor(color);} 
    protected void setLastModified(LocalDateTime lastModified){foo.setLastModified(lastModified);} 
} 

Infrastruktur-Schicht:

// Defines the database model for Foo 
// Only concerned about the table structure 
@Entity 
@Table(name="TBL_FOO") 
public class FooEntity extends FooDecorator { 
    public FooEntity(Foo foo) { 
     super(foo); 
    } 

    @Id @AutoGenerated @Column(name="ID_FOO") 
    protected Long getId() { 
     return super.getId(); 
    } 

    @Column(name="DS_COLOR", length="255") 
    protected String getColor(){ 
     return super.getColor(); 
    } 

    @Temporal @Column(name="DT_MODIFIED") 
    protected LocalDateTime getLastModified(){ 
     return super.getLastModified(); 
    } 

} 
Schicht

Domain (Geschäft)

public class FooBar extends FooEntity { 

    public FooBar(Foo foo) { 
     super(foo); 
    } 

    //let's open the ID for the outside world 
    public Long getId() { 
     return super.getId(); 
    } 

    // Paint with a red color 
    public void paintAs(Color color) { 
     super.setColor(color.getKey()); 
    } 

    // Upper level may want to know the current color 
    public Color getColor() { 
     return Color.parse(super.getColor()); 
    } 

    public boolean isModifiedSince(LocalDateTime compare) { 
     return DateUtils.compareMillis(super.getLastModified(), compare) > 0; 
    } 

    public LocalDateTime getLastModified() { 
     return super.getLastModified(); 
    } 

} 

Applikation (Ansicht) Schicht

/** 
* JSON Eg.: 
* {fooBarId: 1, fooBarColor: 'RED'} 
*/ 
public class FooBarView extends FooBar { 
    public FooBarView(Foo foo) { 
     super(foo); 
    } 

    // Maps field to JSON as 'fooBarId' 
    @JsonMap("fooBarId"); 
    public Long getId() { 
     return super.getId(); 
    } 

    // Maps field to JSON as 'fooBarColor' 
    @JsonMap("fooBarColor") 
    public String getColor() { 
     return super.getColor().toString(); 
    } 

} 

-

// Pseudo Code 
public class FooBarREST { 
    // '/api/v1/foobar/{id}' 
    public getFooBar(Long id) { 
     return new FooBarView(find(id)); 
    } 
} 
+0

Ich möchte einen Anwendungsfall im Code sehen, um sicherzustellen, dass ich Ihren Standpunkt verstanden habe. Dann würde ich gerne meine Gedanken teilen :) – mgonzalezbaile

+0

Dies ist eine große Frage für [Programmierer] (http://programmers.stackexchange.com/), aber ich denke nicht, dass es hier in SO passt. –

+0

@mgonzalezbaile Ich habe einige Pseudo-Code geschrieben, aber ich konnte nicht in einem anspruchsvolleren Geschäftsablauf denken. – ggdio

Antwort

5

Also ... was ich frage, ist für Vorschlag, Tipps und einige Meinungen, wenn es ein guter Ansatz ist.

Ich bin skeptisch. Nenne es zwei 9s.

Architektonisch deutet Ihre Skizze an, dass diese isolierten Komponenten Peers sind, was meiner Meinung nach keine praktische Möglichkeit darstellt, die Bedenken überhaupt zu modellieren. Domain ist für das Geschäft enorm wichtig. Persistenz, nicht so sehr (wenn wir genug Speicher hätten und unsere Server laufen lassen könnten, würden wir uns nicht darum kümmern).

Darüber hinaus gibt es Entwurfsoptionen, die ineinander bluten; Wenn Ihre Wahl der Persistenz Ereignishistorien speichert, dann müssen sich Ihr Domänenmodell und Ihre Repositories darauf einigen, und die Verträge zwischen diesen beiden Komponenten sollten explizit darüber sein - doch keiner Ihrer anderen Komponenten interessiert sich für den Zustand einer Sache implementiert ist, benötigen sie nur eine Abfrageoberfläche.

Und vielleicht nicht einmal das - die Anwendung reagiert auf Anfragen vom Client mit Darstellungen, keine Objekte; Wenn diese Repräsentationen im Voraus zwischengespeichert werden (CQRS), sind die Objekte im Domänenmodell überhaupt nicht relevant.

Darüber hinaus denke ich, die Architektur wie gezeichnet trivialisiert die wahre Komplexität der Abhängigkeiten. Ein Teil der Erkenntnis, dass es sich um verschiedene Komponenten mit unterschiedlichen Interessen handelt, ist, dass Sie sie gegeneinander austauschen können. Jeder API-Wechsel sollte kein "rebuild-the-world" -Ereignis sein (denke semantische Versionierung).

Klärungen nach Beispielcode hinzugefügt hinzugefügt

// Abstract Foo 
public abstract class AbstractFoo { 
    protected Long id; 
    protected String color; 
    //... 
} 

// Abstract Decorator(wraps foo) 
public abstract class FooDecorator extends AbstractFoo { 
    // ... 
} 

Das ist gerade kaputt ist - warum sollte man jeden Dekorateur haben will eine eigene Kopie des Staates ist?

Teil des Problems, denke ich, ist, dass Sie das Decorator Muster mit dem Muster Adapter verwechselt haben.

public interface Foo { 
    Long getId(); 
    Color getColor(); 
    LocalDateTime getLastModified(); 
} 

public interface FooDTO { 
    Long getId(); 
    String getColor(); 
    LocalDateTime getLastMofified(); 
} 

Dies sind Adapter:

public class FooDTOAdapter implements FooDTO { 
    private final Foo foo; 

    // ... 
    String getColor() { 
     return foo.getColor().toString(); 
    } 
} 

public class FooAdapter implements Foo { 
    private final FooDTO dto; 

    // ... 
    Color getColor() { 
     return Color.parse(dto.getColor()); 
    } 
} 

Dies sind Dekorateure, obwohl sie nicht sehr guten sind - siehe unten

public class FooBarView implements FooDTO { 
    private final FooDTO dto; 

    //... 

    // Maps field to JSON as 'fooBarColor' 
    @JsonMap("fooBarColor") 
    public String getColor() { 
     return dto.getColor(); 
    } 
} 

@Entity 
@Table(name="TBL_FOO") 
public class FooEntity implements FooDTO { 
    private final FooDTO dto; 

    // ... 

    @Column(name="DS_COLOR", length="255") 
    public String getColor(){ 
     return super.getColor(); 
    } 
} 

Ich denke, das Annäherung nähert sich dem, was Sie wollen. etwas sieht aus wie

class Connector implements FooRepository { 
    private final Store<FooEntity> entityStore; 

    //... 

    void save(Foo foo) { 
     FooDTO dto = new FooDTOAdapter(foo); 
     FooEntity entity = new FooEntity(dto); 
     entityStore.save(dto); 
    } 
} 

So ist die gute Nachricht, dass jeder

interface FooRepository { 
    save(Foo foo); 
}    

Und die Umsetzung, die das Modell auf das Unternehmen zu speichern verbindet, ist: Zum Beispiel sieht die Signatur des Repository verwendet durch das Modell wie Ihrer Komponenten wird der Zustand durch sein bevorzugtes Objektiv betrachtet, ohne dass tatsächlich irgendwelche der Daten kopiert werden müssen.

Sie sollten jedoch beachten, dass jedes Mal, wenn die Daten eine Ebene durchlaufen, die Perle größer wird, da die Schnittstellen ausgetauscht werden, um sie an die neue Komponente anzupassen. Das ist nicht gut oder schlecht, es ist nur eine Sache, die man sich bewusst ist; Da Sie sich entscheiden, die Daten nicht an der Schnittstelle zu kopieren, wird es weiter entfernt.

FooBarView wurde oben als Dekorateur implementiert; Es ist kein gutes Beispiel, da dies nicht die Rolle ist, die die Implementierung spielt (FooEntity hat das gleiche Problem); Sie wickeln FooDTO nur in FooBarView ein, weil Sie es gerade an eine Serialisierungsschicht übergeben wollen.

class FooBarViewWriter { 
    void writeTo(JsonWriter json, FooBarView view) { 
     // ... 
    } 
} 

Das Stück kümmert sich um die Anmerkungen, die Magie zu tun, aber es kümmert sich nicht darum eigentlich um die FooDTO Oberfläche. Diese Implementierung würde

public class FooBarView /* Not a FooDTO */ { 
    private final FooDTO dto; 

    //... 

    // Maps field to JSON as 'fooBarColor' 
    @JsonMap("fooBarColor") 
    public String getColor() { 
     return dto.getColor(); 
    } 
} 

Mit anderen Worten genauso gut funktionieren, es ist nur ein Adapter ist, dass in dem Dekorierermuster versehentlich geschrieben. Sie erhalten einige Kompilierzeitüberprüfungen, bei denen Sie die gesamte Signatur implementiert haben, aber nicht viel mehr.

Ein wahrscheinlicherer Decorator ist einer, der eine Aspekt zu einer Implementierung hinzufügt.

public class TimedFooRepository implements FooRepository { 
    private final FooRepository repo; 

    public void save(Foo foo) { 
     Timer timer = start(); 
     try { 
      repo.save(foo); 
     } finally { 
      stop(timer); 
     } 
    } 

    // ... 
} 

Abstrakt Zierer kommen in der Regel über, wenn Sie mehrere Implementierungen haben, die gerade gehen, um den Anruf an die innere Schicht versenden.Anstatt diesen Code immer und immer wieder zu schreiben, können Sie es einmal schreiben, und dann lassen Sie die konkreten Implementierungen wählen, und wählen, wo sie das Standardverhalten

abstract class AbstractFooRepository implements FooRepository { 
    private final FooRepository repo; 

    protected AbstractFooRepository(FooRepository repo) { 
     this.repo = repo; 
    } 

    public void save(Foo foo) { 
     repo.save(foo); 
    } 

    // ... 
} 

public class TimedFooRepository extends AbstractFooRepository { 
    // No longer necessary to keep our own handle 
    /* private final FooRepository repo; */ 

    public TimedFooRepository(FooRepository repo, ...) { 
     super(repo); 
     // ... 
    } 

    public void save(Foo foo) { 
     Timer timer = start(); 
     try { 
      super.save(foo); 
     } finally { 
      stop(timer); 
     } 
    } 

    // ... 
} 

Eine abstrakte Dekorateur mit nur einer einzigen Methode ersetzen, müssen ziemlich albern ist, da jede Implementierung diese Methode umschreiben wird. Aber es illustriert die Idee.

+0

Ich würde hinzufügen, dass DTOs in der Praxis selten aus Domänenobjekten konstruiert werden und dass Domänenobjekte normalerweise direkt ohne eine zusätzliche Abstraktionsschicht zugeordnet werden können (z. B. NHibernate mit XML-Zuordnungen). DTOs sind auch Werte und sollten unveränderlich sein ... – plalx