Architecture Team Scratch Pad - Exceptions, Transactions, Sessions, Data Transfer Object, ...

From OpenPetra Wiki
Revision as of 15:41, 18 May 2011 by Christiankatict (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

DISCLAIMER: SCRATCH PAD ONLY!

This page is a dumping ground of things that are currently discussed between ChristianK and ThiasG.

THIS PAGE DOES NOT LAY DOWN A CURRENT OR FUTURE POLICY. SPECIFICALLY, YOU SHOULD NOT IMPLEMENT ANYTHING MENTIONED ON THIS PAGE OR BASE A CURRENT IMPLEMENTATION ON ASSUMPTIONS THAT SOMETHING IN THIS PAGE MIGHT/WILL BECOME REALITY.

There is no reason why you should not read this, but there is absolutely no guarantee that any of the content pasted in here will become reality in some form or other. Our thinking on some of the topics is not finished yet but we need a place to write things down so we can collaborate well, and this is the place for it.

(Some or all of this page may be written in German. Sorry about this!)


Wrapper-Objekt fuer Connector-Klassen

Konzept

Ein Wrapper-Objekt soll automatisch fuer jede serverseitige Klasse erzeugt werden, die mit einem neu zu schaffendem C#-Attribut ClientConnectorAttribute() dekoriert ist (nant generateORM soll diese Klassen generieren). Die (nicht-statische) serverseitige Klasse selbst wird zum Zielobjekt, dass fuer den Client transparent in dem (nicht-statischen) Wrapper-Objekt abgebildet wird. (Dies ist sehr aehnlich zu dem Konzept der 'Instantiators' - ja sogar so aehnlich, dass die 'Instantiators' die Wrapper-Objekt-Funktionen bekommen koennten und auf 'Wrapper' umbenannt werden koennten!).

Der Client ruft Methoden auf dem Wrapper-Objekt auf und gibt immer einen SessionIdentifier mit (diesen hat der Client bekommen, als er beim oeffnen eines Screens den Session Manager um eine neue Session bat). Anhand des SessionIdentifiers holt das Wrapper-Objekt die korrekte Instanz der Session und gibt diese Instanz an alle Methoden weiter, die es im Zielobjekt aufruft.

Das Wrapper-Objekt kuemmert sich auch um die Steuerung der DB Transactions (siehe naechster Absatz). Allerdings macht das Wrapper-Objekt nie ein Commit automatisch; Commits muss der Programmierer manuell an beliebigen Punkten in seinem Code im Zielobjekt machen!!! (Commits und Rollbacks erfolgen auf dem Session Object und nicht direkt auf GDBAccessObj!!!)

Abfolge von Methodenaufrufen die vom Client kommen (komplett generierter Code!)

1) Instanz der Session vom SessionManager holen (anhand des SessionIdentifiers)

2) DB Transaction starten (mit erforderlichem IsolationLevel, welcher in einem Argument des neu zu schaffendem C# Attributs RemoteCall() spezifiziert werden muss)

3) try

3a) Aufruf von eigentlicher Methode im Zielobjekt (diese Methode muss im Zielobjekt mit dem C# Attribut ClientConnectorMethodAttribute() dekoriert sein; dadurch wird diese Methode in diesem Wrapper vorhanden sein). Die Instanz der Session wird als erstes Argument mitgegeben.

4) catch(Exception Exp)

4a) spezifisches Logging (Methodennamen und Zielobjekt erwaehnen) + LogStrackTrace

4b) Wert von Exp in internen Variable merken!

4c) throw Exp

5) finally (IMMER)

5a) try

5aa) DB Rollback

5b) catch(Exception Exp)

5ba) spezifisches Logging (Methodennamen und Zielobjekt erwaehnen) & spezieller Hinweis auf unerwartete Rollback-Exception + LogStrackTrace

5bb) Exp.InnerException auf zuvor gemerkte InnerException setzen!

5bc) throw Exp (mit gesetzter InnerException!)


Sessions

Konzept

Derzeit koennen Daten am Server zwischen unterschiedlichen Servercalls nur erhalten bleiben, wenn UIConnectors verwendet werden und diese durch den 'KeepAlive'-Prozess 'am Leben' gehalten werden (sie sind stateful objects).

Durch die Einfuehrung von 'Sessions' koennte auf stateful objects und daher auf UIConnectors und das 'am-Leben-halten' dieser Objekte verzichtet werden (die statischen WebConnectors koennten anstelle der UIConnectors treten und dann einfach nur noch 'Connectors' heissen). Dies wuerde geringere Komplexitaet und erhoehte Flexibilitaet bedeuten, denn mit 'Sessions' koennen Daten beliebig lange im Serverspeicher gehalten werden, wenn dies noetig sein sollte. Dies kann fuer komplexe Client-Server-Abwicklungen vom Vorteil sein (u.a. fuer DB Transactions spanning multiple Server calls) und auch, wenn der 'Client' etwa eine Website oder irgendein serverseitiges Skript ist, das stateful objects nicht mittels KeepAlive-Signalen 'am Leben halten' koennte (so ein Client beherrscht naemlich womoeglich gar kein .NET Remoting).

Uebersicht ueber die Implementation

  • Es wuerde ein Session Manager-Objekt auf der Serverseite geben
    • ein globales Objekt pro AppDomain [=Client Session]
  • Pro Kontext (e.g. Screen) wuerde auf Anforderung vom Client eine separate Session auf dem Server erzeugt werden (2 Partner Edit Screens parallel geoeffnet = 2 Sessions offen)!
    • Daraus ergibt sich, dass wir kein 'globales' Session-Objekt fuer eine AppDomain [=Client Session] haben (nicht wie in ASP.NET)!

Session Manager

Globaler, statischer 'Session Manager' TSessionManager (neu zu schaffen und einmal beim Einrichten der Client AppDomain zu erzeugen) muss folgendes erlauben und neu das zu schaffende Interface ISessionManagerInterface implementieren:

  • string CreateSession() [erzeugt ein neues Session-Objekt und liefert dessen Session Identifier zurueck], void CloseSession(string SessionIdentifier) [verwirft die Session und somit alle ihre von ihr gehaltenen Daten].
  • Hat eine Methode string CreateSession(out TSession ASession) [erzeugt ein neues Session-Objekt und liefert dessen Session Identifier sowie die erzeugte Session zurueck]. Diese Methode wird nicht im Interface deklariert, denn sie darf nur auf Server-seite verwendet werden (Sessions werden nicht remoted!)
  • Hat eine Methode TSession GetSession(string ASessionIdentifier). Diese Methode wird nicht im Interface deklariert, denn sie darf nur auf Server-seite verwendet werden (Sessions werden nicht remoted!)
  • Referenz zu dieser TSessionManager-Instanz wuerde zukuenftig einmalig bei TClientManager.ConnectClient(...) als ISessionManagerInterface zum Client remoted
    • ISessionManagerInterface muss dafuer in einer *.Shared.*.DLL deklariert sein
    • TSessionManager muss daher nicht in einer *.Shared.*.DLL deklariert sein, sondern kann und soll in einer *.Server.*.DLL deklariert sein
  • Hat ein Dictionary<string, TSession> FSessions
    • Der Dictionary-string-Key ist der SessionIdentifier

Session Objects

'Session'-Objekte (TSession, neu zu schaffen) wuerden nur auf Serverseite existieren (werden nicht remoted)!

  • Lifetime
    • Wird von Client kontrolliert werden (z.b. beim Screen-oeffnen und schliessen): TSessionManager.CreateSession(), TSessionManager.CloseSession().
  • Haben einen Session Identifier (string mit GUID als Inhalt), welcher im Konstruktor generiert wird (readonly Property string SessionIdentifier)
  • Haben eine interne ID, welche im Konstruktor generiert wird (int FSessionID)
  • Haben ein Dictionary<string, ISessionTargetObjectInterface> FSessionTargetObjects
    • ISessionTargetObjectInterface braucht Methoden TDataTransferObject GetData(TSession ASession) (3-fach ueberladen) und TSubmitChangesResult SubmitChanges(TSession ASession, TDataTransferObject ADTO)
    • ISessionTargetObjectInterface braucht void BeforeCommit(TSession ASession) & void AfterCommit(TSession ASession) und void BeforeRollback(TSession ASession) & void AfterRollback(TSession ASession)-Methoden sowie eine void StartTransaction(TSession ASession, IsolationLevel AIsolationLevel)-Methode
      • Über den Isolation-Level bin ich nach wie vor unglücklich. Ich will den gar nicht: Ist eine Fehlerquelle. Die Entwickler sollen lieber bewußt entscheiden, welche Daten sie mit SELECT * FROM >table< WHERE >condition< FOR UPDATE sperren. --Thiasg 13:07, 16 May 2011 (UTC)
        • Darueber muessen wir noch mehr reden. --Christiankatict 13:24, 18 May 2011 (UTC)
  • Haben eine Methode 'void RegisterSessionTargetObject(string ASessionTargetName, ISessionTargetObjectInterface ASessionTarget)' fuer das registrieren von beliebig vielen 'Target Objects'.
  • Haben eine eigene DB Connection (TDataBase FDBAccessObj) auf der die DB Transactions durchgefuehrt werden
    • Damit wuerde es moeglich, dass man 'nested Transactions' nachbildet (welche es in ADO.NET nicht gibt), indem man mehrere 'Sessions' oeffnet (und damit mehrere DB Connections) und die DB Transactions auf diesen sehr sorgfaeltig koordiniert.
  • Haben eigene CommitTransaction()- und RollbackTransaction()-Methoden
    • Wirken direkt auf FDBAccessObj.
      • Zusaetzlich: jeder Commit()-Aufruf erzeugt gleich wieder eine neue DB Transaction auf FDBAccessObj (mit demselben IsolationLevel als die gerade committete Transaction!) und ruft die void StartTransaction(TSession ASession, IsolationLevel AIsolationLevel)-Methode in einer for-loop auf 1..n ISessionTargetObjectInterface-Objekten auf
      • Zusaetzlich: jeder Commit()- und Rollback-Aufruf ruft in einer for-loop 1..n ISessionTargetObjectInterface-Objekte .BeforeCommit & .AfterCommit / bzw .BeforeRollback & .AfterRollback-Methoden auf!
        • Diese Verstaendigungen dienen nicht zum DB-Transaktionshandling in solcherart registrierten Objekten, sondern um e.g. externe Resourcen konsistent zu halten.
  • Haben eigene Save()- und Rollback(string)-Methoden sowie eine RollbackToLastSavepoint()-Methode [nur wenn wir Savepoints implementieren koennen]
    • Ich würde die Methoden AddSavepoint(), bzw. RollBackToSavepoint(string) nennen. Dann werden sie nicht mit Save im Sinne von Abspeichern und kompletten Rollback verwechselt. Diese beiden Methoden sind die Kür, bei der unklar ist, ob sie gebraucht werden. Evtl. sind sie bei einem Wizard hilfreich. Dann müssen sie aber vom Client aufrufbar sein. --Thiasg 13:11, 16 May 2011 (UTC)
      • Umbenennen macht Sinn.
      • Ob wir diese Methoden von der Clientseite aufrufen lassen muessen wir uns anhand konkreter Problemstellungen ueberlegen. (Derzeit jedenfalls nicht aktuell, weil wir noch gar nicht wissen, ob wir Savepoints implementieren koennen) --Christiankatict 13:41, 18 May 2011 (UTC)
    • Wirken direkt auf FDBAccessObj
      • Die string Save()-Methode akzeptiert keinen String (im Gegensatz zu der ADO.NET-Methode!), sondern liefert einen eindeutigen String zurueck (e.g. interne ID + Serverzeit in Millisekunden)
  • Haben eine TDataTransferObject GetData()-Methode
    • diese Methode...
      • erzeugt eine Instanz eines 'Data Transfer Object' vom neuen Typ 'TDataTransferObject' (siehe unten)
      • ruft die Methode GetData() in einer for-loop auf 1..n ISessionTargetObjectInterface-Objekten auf
      • fuegt jedes von diesen Methoden retournierte TTypedDataSet mittels Methode 'void AddDataSet(TTypedDataSet ADataSet)' in die vom Data Transfer Object gehaltene Liste von Typed DataSets ein.
  • Haben eine TSubmitChangesResult SubmitChanges(ref TDataTransferObject ADTO)-Methode
    • diese Methode...
      • akzeptiert ein 'Data Transfer Object'-Instanz vom neuen Typ 'TDataTransferObject' (siehe unten)
      • ruft die Methode TSubmitChangesResult SubmitChanges(TSession ASession, TDataTransferObject ADTO) in einer for-loop auf 1..n ISessionTargetObjectInterface-Objekten auf
        • wenn bei einem Aufruf ein Fehler auftritt, duerfen die weiteren Aufrufe nicht mehr durchgefuehrt werden
      • meldet jeglichen Fehler zum Client

Base Classes

  • Private Base Class 'SessionTargetObjectBaseEmpty'. Implementiert ISessionTargetObjectInterface
    • implementiert leere Methoden void BeforeCommit(TSession ASession) & void AfterCommit(TSession ASession) und void BeforeRollback(TSession ASession) & void AfterRollback(TSession ASession)-Methoden sowie leere void StartTransaction(TSession ASession, IsolationLevel AIsolationLevel)-Methode
  • Private Base Class 'SessionTargetObjectBaseDataSet'. Erbt von SessionTargetObjectBaseEmpty
    • hat protected TTypedDataSet FDataSet
  • evtl. zukuenftigte private Class 'SessionTargetObjectCache'. Implementiert ISessionTargetObjectInterface
    • haelt beliebige Daten in beliebigen zu definierenden Strukturen in einem Cache (evtl. ein DataSet mit 1..n DataTables [so wie unser existierender TCacheableTablesManager])
  • evtl. zukuenftigte private Class 'SessionTargetObjectBaseExternalSystems'. Implementiert ISessionTargetObjectInterface (oder erbt von SessionTargetObjectBaseEmpty)
    • beliebige Implementationsdetails...


Data Transfer Object

Das 'TDataTransferObject' (neu zu schaffen) wird ein universelles Objekt zur Uebertragung von Daten vom Server zum Client und vom Client zum Server sein.

Es wuerde die bisher eingesetzten strongly Typed DataSets ersetzen. Der Vorteil liegt v.a. in der Flexibilitaet, 1..n TTypedDataSets halten zu koennen.

  • Enthaelt 0..n TTypedDataSet-Objekte, welche mit String-Identifier oder Index ansprechbar sind
    • intern in einem Dictionary<string, TTypedDataSet> FDataSets abgelegt
      • im Typed DataSet 'x' (e.g. PartnerEditTDS): jetztige TTypedDataSet-Definition (eg. PartnerEditTDS) ABZUEGLICH CustomTable(s).
      • in 1..n weiteren Typed DataSets: Daten speziell fuer Organisation 'X' oder Organisation 'Y'.
      • im Typed DataSet 'CustomTables': enthaelt 1..n CustomTable(s) fuer 1..n DataSets welche in 'x' oder in den DataSets fuer andere Organisationen abgelegt sind.
      • Problem mit diesem Ansatz: sollte es Definitionen von 'CustomRelations' in der XML-Datei welche die TTypedDataSets spezifiziert geben welche 'CustomTables' mit 'Tables' in demselbem DataSet verknuepft, so wuerden diese Relations nicht machbar sein wenn die betroffenen DataTables in unterschiedlichen DataSets zu finden sind!
  • Hat Methoden 'void AddDataSet(TTypedDataSet ADataSet)' sowie 'TTypedDataSet GetDataSet(string ADataSetName)' und TTypedDataSet GetDataSet(int ADataSetIndex).
  • Das 'Data Transfer Object' wird bei TDataTransferObject GetData(...) und bei SubmitChanges(TDataTransferObject ADTO, ...) uebermittelt. Es werden daher nicht laenger direkt strongly Typed DataSets (eg. PartnerEditTDS) uebergeben!


GBAccess Object

  • Evtl. etwas mehr 'verstecken', denn der Programmierer braucht es i.d.R. nicht mehr zu benutzen, wenn oben aufgefuehrte Arbeitsweisen gaengig werden???
    • - ABER es gibt immer noch viele Situationen auf der Serverseite, wo auch in Zukunft keine Sessions benutzt werden (e.g. die vielen Server-interne Dinge die keine Client-Interaktion haben)!
      • Dann fordert er sich auch eine Session an und hat damit eine DB Session :-) --Thiasg 12:58, 16 May 2011 (UTC)
        • Das ist durchaus ein Ansatz, darueber sollten wir noch mehr nachdenken. --Christiankatict 13:27, 18 May 2011 (UTC)


DB Transactions spanning multiple Server calls

TODO


BackgroundworkManager Object (geringe Prioritaet)

  • Globales Objekt auf Clientseite um 'delayed data loading' komplett in Hintergrund-Threads abwickeln zu koennen (e.g. im Partner Edit Screen: Daten fuer 'Addresses'- und 'Subscriptions'-Tabs immer im Hintergrund laden, auch wenn zu allererst ein anderes Tab [e.g. 'Partner Types'] vom User geoeffnet wurde)
    • Hat einen internen Thread-Pool von max. eg. 3 'Worker Threads', die von beliebigen Stellen im Client fuer Hintergrundaufgaben herangezogen werden koennen, wenn diese Hintergrundaufgaben nicht innerhalb einer bestimmten Zeit ausgefuehrt werden muessen
      • Wuerden mehr Worker Threads angefordert als im internen 'Thread Pool' abgearbeitet werden koennen, wuerden diese Anfragen in eine Queue gestellt. Diese 'gequeueten' Anfragen wuerden nach und nach in den 'Thread Pool' rutschen und anschliessend abgearbeitet werden, sobald mindestens einer der bisher laufenden Threads im 'Thread Pool' beendet wurde.
    • Aufpassen: Synchroniserung des Datenladens so absichern dass sicher keine (evtl. veraenderten) lokalen Daten ueberschrieben werden koennen, egal wie lange der asynchrone Servercall dauert (der Benutzer koennte inzwischen lokale Daten geaendert haben die mit dem Rueckgabewert des Servercalls ueberschrieben werden koennten)!!!


Weitere Diskussionspunkte

?????