Architecture Team Scratch Pad - Exceptions, Transactions, Sessions, Data Transfer Object, ...
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!)
DB Transactions spanning multiple Server calls
TODO
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 beiTClientManager.ConnectClient(...)
alsISessionManagerInterface
zum Client remotedISessionManagerInterface
muss dafuer in einer *.Shared.*.DLL deklariert seinTSessionManager
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 derSessionIdentifier
- Der Dictionary-
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()
.
- Wird von Client kontrolliert werden (z.b. beim Screen-oeffnen und schliessen):
- 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 MethodenIDataTransferObjectInterface GetData(TSession ASession)
(3-fach ueberladen) undTSubmitChangesResult SubmitChanges(TSession ASession, IDataTransferObjectInterface ADTO)
ISessionTargetObjectInterface
brauchtvoid BeforeCommit(TSession ASession)
&void AfterCommit(TSession ASession)
undvoid BeforeRollback(TSession ASession)
&void AfterRollback(TSession ASession)
-Methoden sowie einevoid 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)
- Ü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
- 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()
- undRollbackTransaction()
-Methoden- Wirken direkt auf
FDBAccessObj
.- Zusaetzlich: jeder
Commit()
-Aufruf erzeugt gleich wieder eine neue DB Transaction aufFDBAccessObj
(mit demselben IsolationLevel als die gerade committete Transaction!) und ruft dievoid StartTransaction(TSession ASession, IsolationLevel AIsolationLevel)
-Methode in einer for-loop auf 1..nISessionTargetObjectInterface
-Objekten auf - Zusaetzlich: jeder
Commit()
- undRollback
-Aufruf ruft in einer for-loop 1..nISessionTargetObjectInterface
-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.
- Zusaetzlich: jeder
- Wirken direkt auf
- Haben eigene
Save()
- undRollback(string)
-Methoden sowie eineRollbackToLastSavepoint()
-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)
- 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)
- Die
- Wirken direkt auf
- Haben eine
IDataTransferObjectInterface 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..nISessionTargetObjectInterface
-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.
- erzeugt eine Instanz eines 'Data Transfer Object' vom neuen Typ '
- diese Methode...
- Haben eine
TSubmitChangesResult SubmitChanges(ref IDataTransferObjectInterface ADTO)
-Methode- diese Methode...
- akzeptiert ein 'Data Transfer Object'-Instanz vom neuen Typ '
TDataTransferObject
' (siehe unten) - ruft die Methode
TSubmitChangesResult SubmitChanges(TSession ASession, IDataTransferObjectInterface ADTO)
in einer for-loop auf 1..nISessionTargetObjectInterface
-Objekten auf- wenn bei einem Aufruf ein Fehler auftritt, duerfen die weiteren Aufrufe nicht mehr durchgefuehrt werden
- meldet jeglichen Fehler zum Client
- akzeptiert ein 'Data Transfer Object'-Instanz vom neuen Typ '
- diese Methode...
Base Classes
- Private Base Class '
SessionTargetObjectBaseEmpty
'. ImplementiertISessionTargetObjectInterface
- implementiert leere Methoden
void BeforeCommit(TSession ASession)
&void AfterCommit(TSession ASession)
undvoid BeforeRollback(TSession ASession)
&void AfterRollback(TSession ASession)
-Methoden sowie leerevoid StartTransaction(TSession ASession, IsolationLevel AIsolationLevel)
-Methode
- implementiert leere Methoden
- Private Base Class '
SessionTargetObjectBaseDataSet
'. Erbt vonSessionTargetObjectBaseEmpty
- hat
protected TTypedDataSet FDataSet
- hat
- evtl. zukuenftigte private Class '
SessionTargetObjectCache
'. ImplementiertISessionTargetObjectInterface
- haelt beliebige Daten in beliebigen zu definierenden Strukturen in einem Cache (evtl. ein DataSet mit 1..n DataTables [so wie unser existierender
TCacheableTablesManager
])
- haelt beliebige Daten in beliebigen zu definierenden Strukturen in einem Cache (evtl. ein DataSet mit 1..n DataTables [so wie unser existierender
- evtl. zukuenftigte private Class '
SessionTargetObjectBaseExternalSystems
'. ImplementiertISessionTargetObjectInterface
(oder erbt vonSessionTargetObjectBaseEmpty
)- 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.
- Implementiert neu zu schaffendes Interface '
IDataTransferObjectInterface
'.- UNKLAR: Wofuer brauchen wir das Interface - remoten wir nicht das ganze Objekt, sondern nur das Interface? Wenn ja, warum (womoeglich Zusammenhang mit
Commit()
- undRollback()
-Methoden [siehe unten])? - Eigentlich ist es ein dummes Objekt (Hash Dictionary) --Thiasg 13:01, 16 May 2011 (UTC)
- UNKLAR: Wofuer brauchen wir das Interface - remoten wir nicht das ganze Objekt, sondern nur das Interface? Wenn ja, warum (womoeglich Zusammenhang mit
- 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
) ABZUEGLICHCustomTable(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!
- im Typed DataSet 'x' (e.g.
- intern in einem
- Hat Methoden '
void AddDataSet(TTypedDataSet ADataSet)
' sowie 'TTypedDataSet GetDataSet(string ADataSetName)
' undTTypedDataSet GetDataSet(int ADataSetIndex)
. - Das 'Data Transfer Object' wird bei
IDataTransferObjectInterface GetData(...)
und beiSubmitChanges(IDataTransferObjectInterface ADTO, ...)
uebermittelt. Es werden daher nicht laenger direkt strongly Typed DataSets (eg.PartnerEditTDS
) uebergeben! - Hat
Commit()
- undRollback()
-Methoden.- UNKLAR: Wofuer diese Methoden und was sollen diese bewirken? (Vielleicht war dies nur ein Missverstaendnis?)
- Das ist wohl das Mißverständnis, was von den vorherigen Session-Diskussionen kommt: Das DTO braucht es nicht. --Thiasg 13:01, 16 May 2011 (UTC)
BackgroundworkManager Object
- 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)!!!
- 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
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)
- Dann fordert er sich auch eine Session an und hat damit eine DB Session :-) --Thiasg 12:58, 16 May 2011 (UTC)
- - 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)!
Weitere Diskussionspunkte
?????