9 Stimmen

Ist es schlechtes Design, wenn Ihre Business(Service)-Ebene von einer Benutzersitzung abhängig ist?

In einer gewöhnlichen MVC-entworfenen Anwendung ist es eine schlechte Idee, die Servic-Ebene von einer Benutzersitzung abhängig zu machen? Angenommen, es gibt eine Service-Methode, die ein paar Objekte aus einer Datenbank abruft, und Sie möchten unterschiedliche Ergebnisse zurückgeben, abhängig davon, wer den Aufruf initialisiert - zum Beispiel könnte ein Administrator 10 Zeilen von Objekten erhalten, während ein normaler Benutzer möglicherweise nur 7 Zeilen erhält, weil die letzten 3 "nur für Administratoren" sind. Ein paar Möglichkeiten, dies zu lösen, wären:

  • Einführung eines neuen Methodenparameters, in dem Sie den aufrufenden Benutzer einschließen. Abhängigkeitslos, aber umständlich, den Benutzerparameter in vielen Methoden einfügen zu müssen.
  • Erstellen Sie verschiedene Methoden für verschiedene Benutzerrollen (mit mehreren Ergebnissen). Auch abhängigkeitslos, aber viele Methoden, die im Grunde dasselbe tun, was das Risiko von Code-Duplizierung erhöht.
  • Lassen Sie die Methode aus einem ThreadLocal-Variablen in einem statischen Kontext lesen, die die aktuelle Benutzersitzung speichert. Diese Variable wird vor jedem Aufruf festgelegt.

In letzter Zeit habe ich die letzte Methode immer öfter verwendet, da sie eine saubere Schnittstelle bietet und sich sehr praktisch zu verwenden anfühlt. Ein Filter stellt sicher, dass der aktuelle Thread immer einen Benutzer hat. Ist das schlechtes Design? Ich glaube, dass einige dies als eine Abhängigkeit der Service-Ebene von der Web-Ebene betrachten könnten, obwohl ich persönlich denke, dass sie ziemlich entkoppelt sind. Die größte Konsequenz ist, dass das Verhalten einer Methode je nach Zustand einer anderen Klasse unterschiedlich sein wird, was sowohl positiv als auch negativ sein könnte.

Was denkst du darüber? Wenn es keine gute Lösung ist, was wäre eine stärkere?

5voto

mikera Punkte 103423

Ich rate dringend von ThreadLocal-Stilansätzen ab - es scheint wie ein "globaler Variablen"-Designgeruch. Dies hat viele Probleme, insbesondere:

  • Ihr Code wird schwieriger zu testen, wenn Sie den rutschigen Hang hinuntergehen und immer mehr impliziten globalen Zustand einrichten müssen, bevor Sie testen
  • Sie verlieren die Fähigkeit klar zu sehen, mit welchen Parametern ein bestimmtes Stück Code arbeitet. Dies kann für Wartungspersonal oder wenn Sie nach ein paar Monaten zum Code zurückkehren, sehr verwirrend sein.
  • Es kann sehr böse Fehler / Komplexität verursachen, wenn Sie jemals Arbeit an verschiedene Threads übergeben. Sie haben effektiv Ihre Codeausführung davon abhängig gemacht, auf welchem Thread er läuft.... was kann schon schiefgehen? :-)
  • Sie erstellen zyklische Abhängigkeiten (Service-Schicht <-> Benutzeroberfläche-Schicht). Nie eine gute Idee, Sie sollten versuchen, Abhängigkeiten nur in eine Richtung fließen zu lassen (fast immer Benutzeroberfläche-Schicht -> Service-Schicht)

Zwischen den beiden anderen Ansätzen denke ich, dass es "darauf ankommt":

  • Wenn der Benutzer intrinsischer Bestandteil des Datenmodells ist (z.B. Sie haben eine Social-Graph-Datenbank), dann wäre es natürlich, den Benutzer als Parameter zu übergeben.
  • Wenn die Benutzerdaten nur für Front-End-Dinge wie Authentifizierung usw. verwendet werden, tendiere ich eher dazu, die Abhängigkeit von spezifischen Benutzerdetails zu minimieren und stattdessen unterschiedliche Methoden für verschiedene Rollen zu erstellen (oder äquivalent einen "Rollen"-Parameter hinzuzufügen).

Eine alternative Option ist das Übergeben eines "Kontext"-Objekts an die Service-Schicht, das eine Reihe relevanter Sitzungsdaten enthält (mehr als nur den Benutzernamen). Dies könnte sinnvoll sein, wenn Sie den Parameteraufblähung minimieren möchten. Seien Sie einfach vorsichtig, dass es nicht zu einem Mittel wird, um gute Schichtungsprinzipien zu umgehen. Wenn es nur "Daten" sind, ist es wahrscheinlich in Ordnung, aber sobald Leute Callback-Objekte / UI-Komponenten im Kontext übergeben, neigen Sie wahrscheinlich zu einem Durcheinander....

3voto

dexter meyers Punkte 2768

Verwenden Sie einfach den Rollenmechanismus von Java EE, er ist bereits vorhanden.

Als Beispiel:

@Stateless
public class AService {

    @Resource
    private SessionContext sessionContext;

    @PersistenceContext
    private EntityManager entityManager;

    public List fetchAFewObjects() {

        String query = "aUserQuery";      

        if (sessionContext.isCallerInRole("ADMIN")
            query = "aAdminQuery";

        return entityManager.createNamedQuery(query, AObject.class)
            .getResultList();    
    }  
}

Auf der Webseite stellen Sie sicher, dass Sie ein Container-Login durchführen, damit der Server über den eingeloggten Benutzer informiert ist.

2voto

Mike Braun Punkte 3696

Sie müssen keine der Lösungen für Ihren Anwendungsfall implementieren.

In EJB wird das, was Sie möchten, bereits vom Framework unterstützt. Es wird als Sicherheitskontext bezeichnet und wird automatisch in jeden von Ihnen aufgerufenen Methoden übertragen (hinter den Kulissen wird es tatsächlich oft durch TLS implementiert, aber das ist ein Implementierungsdetail).

Dieser Sicherheitskontext ermöglicht den Zugriff auf den Benutzernamen und seine/ihre Rollen. In Ihrem Fall scheint es, dass Sie nur die Rollen überprüfen müssen.

Sie können dies deklarativ tun, indem Sie Annotationen auf Ihren Methoden verwenden (@RolesAllowed) oder indem Sie den EJB-Sitzungskontext injizieren und ihn nach den Rollen des aktuellen Benutzers fragen.

1voto

lsoliveira Punkte 4372

Von einer architektonischen Perspektive aus betrachtet, denke ich, dass Ihre letzte Option die einzig vernünftige ist.

  1. Stellt ein Sicherheitsrisiko dar. Sie müssten den Benutzernamen gegen die Benutzerinformationen in der Sitzung validieren (oder durch einen anderen Sicherheitsmechanismus), was jeglichen Vorteil ungültig machen würde (da Sie ohnehin bereits den Benutzernamen/Rolle des Benutzers kennen müssten).

  2. Ist nicht wirklich praktikabel oder skalierbar. Stellen Sie sich vor, Sie müssen eine weitere Rolle hinzufügen, die mehr Informationen erfordert (nicht nur mehr Datensätze). Dann hätten Sie Methoden für Ihre bestehenden 2 Benutzerrollen und eine weitere für die neue Rolle, wahrscheinlich mit einer anderen Signatur und einer if-if-if-Logik, um die unterschiedlichen Szenarien zu behandeln. Das neigt dazu, sehr schnell unübersichtlich zu werden und führt zu schwerwiegenden Wartungsproblemen.

Wenn Sie sich Java-Web-Frameworks ansehen, werden Sie feststellen, dass sie den letzten Ansatz verwenden. Sie legen auch die Benutzerinformationen in einer "abstrakteren" Entität (Identität, Kontext usw.) ab und befürworten die Verwendung von Rollen anstelle von Benutzernamen bei der Bearbeitung von Informationsstratifizierung. Auf diese Weise ist es viel einfacher, mit der Komplexität umzugehen, die in größeren Benutzerbasen existiert, in denen mehrere Rollen unterschiedliche Informationsansichten erfordern. Es geht nur darum, Rollen zu Benutzerprofilen hinzuzufügen.

Zur Referenz siehe die Implementierung von Seam: http://grepcode.com/file/repository.jboss.org/nexus/content/repositories/releases/org.jboss.seam/jboss-seam/2.0.0.GA/org/jboss/seam/contexts/Contexts.java#Contexts.

Aktualisierung aufgrund des Kommentars, dass nicht ausreichend Platz vorhanden ist

Basierend auf Ihrem Kommentar unten scheint es, dass Sie Ihren Service- und UI-Layer koppeln. Das kann nur passieren, wenn Sie niemals erwarten, dass externe Entitäten Ihren Service-Layer verbrauchen. Das kann in Ordnung sein, es kommt nur auf Ihr Szenario an.

Abgesehen davon stimme ich Mikera nur bis zu einem gewissen Punkt zu. ThreadLocals sind nicht global im Sinne einer statischen globalen Variable. Für Ihren Service-Layer sind die Benutzerinformationen sicherlich global und es macht Sinn, sie von den standardmäßigen Geschäftsinformationen zu trennen. Denken Sie auch daran, dass Sie möglicherweise andere Informationen haben, die gespeichert werden müssen. Was ist, wenn Sie das Land, die Sprache oder die Währung des Benutzers benötigen?

ThreadLocals bieten eine effektive und leistungsfähige Lösung. Perfekt? Sicherlich nicht, aber es ist etwas sauberer als andere Lösungen, wenn es korrekt implementiert wird. Dennoch erfordert es einige Anstrengungen, um eine ordnungsgemäße und narrensichere Implementierung zu erstellen, aber das kann man von so ziemlich allem sagen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X