Naar aanleiding van dit topic, heb ik besloten om eens een 'concreet' (proef/test/pruts)project uit te werken ivm smart clients.
Ik zal in dit topic een beknopte voorstelling geven van de case, en de architectuur v/h project dat ik tot nu toe uitgewerkt heb voorstellen.
De bedoeling van dit topic is om die architectuur te bespreken, te verbeteren, etc...
Ik heb ook al het 'Offline Application Block' van MS bekeken, maar dit is niet geheel toereikend voor m'n project. Ik heb mij er natuurlijk wel voor bepaalde dingen op gebaseerd.
Case
Ik heb me een bedrijf voorgesteld dat voedingswaren aan huis levert en ook bestellingen aan huis opneemt.
Stel je dus een bedrijf voor waar mensen kunnen naar telefoneren om bepaalde goederen uit een catalogus te bestellen. De bestelde goederen worden dan op een latere dag aan huis geleverd, en ook dan moet het mogelijk zijn dat de consument aan diegene die de goederen levert een nieuwe bestelling plaatst.
De werknemer van dat bedrijf geeft dan de bestelling in in z'n computer (laptop, handheld, whatever, ...). Als de bestelling op dat moment wordt ingegeven, dan zal die computer hoogstwaarschijnlijk niet met het / een (bedrijfs)netwerk verbonden zijn. Dat wil dus zeggen dat de bestelling 'gecached' moet worden op die computer, en later, als er een connectie met een netwerk gedetecteerd wordt, die bestelling automatisch overgepompt wordt naar de centrale server.
Hoe gaat dit in z'n werk:
De werknemer die de goederen levert gaat, voordat hij aan z'n ronde begint, de gegevens die hij nodig heeft (de klanten die hij dus vandaag bezoekt) gaan 'downloaden' in een lokale data-store op z'n computer. Dit zorgt ervoor dat hij over die gegevens beschikt als hij niet met het netwerk verbonden is.
Als hij een bestelling 'offline' ingeeft, dan wordt die bestelling ook in z'n offline datastore ingebracht, en wordt er een message in een queue gezet met de gegevens over die bestelling. Van zodra de applicatie opnieuw online gaat, moeten die messages opgepikt worden en verstuurd worden naar de centrale DB.
Stel nu dat de offline datastore een Access DB is, en de centrale server een SQL Server DB.
Om dit te verwezenlijken heb ik een simpel databankje in elkaar geflanst. Om de scope van het project enigszins te beperken (het is tenslotte maar een leerproject), heb ik de case beperkt tot de bestelling v/d goederen. Het leveren en dergelijke zijn dus buiten beschouwing gelaten.
Het datamodel ziet er zo uit:

ter illustratie
Ik heb een aantal modules geidentificeerd en (gedeeltelijk) uitgewerkt.
Data Access
Het grote probleem hier is dat er zowel online als offline moet kunnen gewerkt worden. Dit wordt opgelost door een offline datastore te voorzien op de computer van de werknemer in de vorm van een Access databank.
De online databank is een SQL Server databank. Dit brengt dus met zich mee dat er 2 Data Access Layers moeten voorzien zijn.
Het probleem is ook dat de implementatie van de DAL laag moet afgeschermd worden van de User Interface. Je kan immers niet op voorhand in de applicatie zeggen welke DAL we gaan gebruiken.
Om dit te verwezenlijken heb ik voor een bridge-design pattern gekozen. Hiermee kan ik nl. de implementatie (Access, SQL Server) scheiden van de abstractie (Klant, Categorie, Product, ....).
Het class diagram ziet er als volgt uit (het diagram is niet volledig uitgewerkt voor de eenvoud, zoals je kan zien staan niet alle classes en methods erop):

clickable
Zoals je kan zien heb ik 2 'concrete' FoodStoreDataImpl classes, eentje voor SQL Server, en eentje voor Access.
Deze classes bevatten voor iedere abstractie (Klant, Categorie, Product, ...) static methods die de gegevens daadwerkelijk naar de DB gaan wegschrijven.
Een FoodStoreObject heeft een reference naar een IFoodStoreDataImpl en gebruikt dan die implementatie om de databank operatie uit te voeren.
Om het even te illustreren met wat code (los uit de hand geschreven omdat ik nog geen code heb.
):
Een FoodStoreObject gebruikt dan een bepaalde implementatie om de DB te gaan aanspreken:
Een volgend probleem is:
Hoe en waar bepalen we welke data implementation er moet gebruikt worden
Kort gezegd: als de applicatie online is, moet de OnlineDataImpl (SQL Server DAL) gebruikt worden, anders de Access DAL.
Om dit te verwezenlijken zullen we dus een soort van 'ConnectionManager' nodig hebben.
Deze ConnectionManager zal dus verantwoordelijk zijn voor het bekijken van de 'netwerkstatus', en de gepaste actie(s) moeten ondernemen als deze status wijzigt.
Het is ook zo dat bepaalde acties offline moeten kunnen uitgevoerd worden, maar andere niet. Het is dan dus ook nodig dat er bepaalde controls v/d applicatie ge-enabled of gedisabled worden, naar gelang v/d connectie-status.
Als de connectiestatus veranderd, zullen er dus acties op Forms moeten gebeuren, en zal de te gebruiken FoodStoreDataImpl moeten veranderen.
Aangezien er slechts 1 ConnectionManager hoeft/mag zijn in de hele applicatie, kan die dus alleszins als een Singleton geimplementeerd worden.
Omdat een Form i/d applicatie moet kunnen reageren op een wijziging v/d connectionstate zal er dus op de een of andere manier communicatie moeten kunnen plaatsvinden tussen een Form en de ConnectionManager.
Om dit zo makkelijk mogelijk op te lossen zal ik gebruik maken van een (multicast) delegate als attribuut v/d ConnectionManager. Deze delegate zal getriggered worden als de connectionstate veranderd.
Om dit te verwezenlijken zal iedere form aan een bepaalde interface moeten voldoen; nl. een interface die ervoor zorgt dat de Form een eventhandler heeft die aan deze delegate kan gehangen worden.
Om dit even met UML te illustreren:

Aangezien .NET ook zoiets kent als 'Form inheritance', kan ik dus een Form maken die die ISmartClientForm implementeert, en waarvan al mijn andere forms dan overerven.
In de OnConnectionStateChanged method van de inherited forms wordt er dan gekeken naar de ConnectionStateEventArgs, en aan de hand van dat argument worden dan bepaalde controls ge-enabled of gedisabled, etc...
De connectionmanager zal alleszins in een aparte thread moeten lopen en geregeld de netwerkstatus 'pollen'. Als die veranderd, dan zal de ConnectionStateChangedEventHandler moeten geraised worden.
De ConnectionStateChangedEventHandler is dan ook de geschikte plaats om de te gebruiken IFoodStoreDataImpl te bepalen.
De IFoodStoreDataImpl wordt echter het best gecreeërd op het moment dat we ze daadwerkelijk nodig hebben (in een IFoodStoreObject dus).
Het creeëren van de juiste IFoodStoreDataImpl doen we dus best in een IFoodStoreObject, en het bepalen welke Data Implementatie we nodig hebben, gebeurt dus best in de ConnectionManager; hiervoor kunnen we een 'Abstract Factory' gebruiken:

De ConnectionManager zal dus een object die de IFoodStoreDALFactory interface implementeerd bevatten.
De ConnectionManager zal dan ook een eigen EventHandler moeten hebben die bij zijn ConnectionStateChangedEvent geregistreerd is. Die eventhandler zal dan het juiste factory object creeëren:
In de IFoodStoreObject - classes kunnen we dan het volgende doen:
In principe heb ik nu dus al de 'grote lijnen' ontworpen om:
- een scheiding te maken tussen de DAL van de online en de offline store;
- de status op de forms te veranderen als de connectiestatus veranderd;
- de juiste DAL implementatie te kiezen als de connectiestatus veranderd.
Nu vraag ik me af wat jullie van deze 'architectuur' vinden. Vinden jullie dat er iets schort, dat er iets beter kan, dat er iets niet goed opgezet is, .... laat het weten. Het is mijn bedoeling om er meer over te leren.
Nu moet ik ook niet iets vinden om die 'Message Queueing te implementeren, maar daar moet ik nog eerst eens eea over lezen, en er eens goed over nadenken. Mijn eerste gedacht zou zijn, dat de messages die in de queue gezet moeten worden in de DAL v/d OfflineDataStore moeten gemaakt worden.
Ik moet ook de ConnectionManager nog verder uitwerken; zo moet de mogelijkheid nog voorzien worden om de ConnectieStatus te gaan detecteren, en bij een verandering de ConnectionStateChanged event te raisen.
Als jullie hier ideeën over hebben, post ze dan ook maar.
Hmmm, ik wist dat dit een lang topic kon worden, maar zo lang...
Hierbij nomineer ik dit topic dan ook maar voor langste topicstart van 2004
Alleszins bedankt aan iedereen die de moed vindt om dit topic te doorworstelen, en mij van input te voorzien.
Ik zal in dit topic een beknopte voorstelling geven van de case, en de architectuur v/h project dat ik tot nu toe uitgewerkt heb voorstellen.
De bedoeling van dit topic is om die architectuur te bespreken, te verbeteren, etc...
Ik heb ook al het 'Offline Application Block' van MS bekeken, maar dit is niet geheel toereikend voor m'n project. Ik heb mij er natuurlijk wel voor bepaalde dingen op gebaseerd.
Case
Ik heb me een bedrijf voorgesteld dat voedingswaren aan huis levert en ook bestellingen aan huis opneemt.
Stel je dus een bedrijf voor waar mensen kunnen naar telefoneren om bepaalde goederen uit een catalogus te bestellen. De bestelde goederen worden dan op een latere dag aan huis geleverd, en ook dan moet het mogelijk zijn dat de consument aan diegene die de goederen levert een nieuwe bestelling plaatst.
De werknemer van dat bedrijf geeft dan de bestelling in in z'n computer (laptop, handheld, whatever, ...). Als de bestelling op dat moment wordt ingegeven, dan zal die computer hoogstwaarschijnlijk niet met het / een (bedrijfs)netwerk verbonden zijn. Dat wil dus zeggen dat de bestelling 'gecached' moet worden op die computer, en later, als er een connectie met een netwerk gedetecteerd wordt, die bestelling automatisch overgepompt wordt naar de centrale server.
Hoe gaat dit in z'n werk:
De werknemer die de goederen levert gaat, voordat hij aan z'n ronde begint, de gegevens die hij nodig heeft (de klanten die hij dus vandaag bezoekt) gaan 'downloaden' in een lokale data-store op z'n computer. Dit zorgt ervoor dat hij over die gegevens beschikt als hij niet met het netwerk verbonden is.
Als hij een bestelling 'offline' ingeeft, dan wordt die bestelling ook in z'n offline datastore ingebracht, en wordt er een message in een queue gezet met de gegevens over die bestelling. Van zodra de applicatie opnieuw online gaat, moeten die messages opgepikt worden en verstuurd worden naar de centrale DB.
Stel nu dat de offline datastore een Access DB is, en de centrale server een SQL Server DB.
Om dit te verwezenlijken heb ik een simpel databankje in elkaar geflanst. Om de scope van het project enigszins te beperken (het is tenslotte maar een leerproject), heb ik de case beperkt tot de bestelling v/d goederen. Het leveren en dergelijke zijn dus buiten beschouwing gelaten.
Het datamodel ziet er zo uit:

ter illustratie
Ik heb een aantal modules geidentificeerd en (gedeeltelijk) uitgewerkt.
Data Access
Het grote probleem hier is dat er zowel online als offline moet kunnen gewerkt worden. Dit wordt opgelost door een offline datastore te voorzien op de computer van de werknemer in de vorm van een Access databank.
De online databank is een SQL Server databank. Dit brengt dus met zich mee dat er 2 Data Access Layers moeten voorzien zijn.
Het probleem is ook dat de implementatie van de DAL laag moet afgeschermd worden van de User Interface. Je kan immers niet op voorhand in de applicatie zeggen welke DAL we gaan gebruiken.
Om dit te verwezenlijken heb ik voor een bridge-design pattern gekozen. Hiermee kan ik nl. de implementatie (Access, SQL Server) scheiden van de abstractie (Klant, Categorie, Product, ....).
Het class diagram ziet er als volgt uit (het diagram is niet volledig uitgewerkt voor de eenvoud, zoals je kan zien staan niet alle classes en methods erop):

clickable
Zoals je kan zien heb ik 2 'concrete' FoodStoreDataImpl classes, eentje voor SQL Server, en eentje voor Access.
Deze classes bevatten voor iedere abstractie (Klant, Categorie, Product, ...) static methods die de gegevens daadwerkelijk naar de DB gaan wegschrijven.
Een FoodStoreObject heeft een reference naar een IFoodStoreDataImpl en gebruikt dan die implementatie om de databank operatie uit te voeren.
Om het even te illustreren met wat code (los uit de hand geschreven omdat ik nog geen code heb.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // deze class bevat de Access implementatie v/d DAL
class OfflineDataStore : IFoodStoreDataImpl
{
public static void InsertKlant ( string klantNaam, string voorNaam, .... )
{
OleDbCommand cmd = new OleDbCommand ();
....
cmd.CommandText = "INSERT INTO tblKlant .....";
cmd.ExecuteNonQuery ();
}
...
public static void UpdateProduct ( int productId, string productNaam, .... )
{
OleDbCommand cmd = new OleDbCommand();
....
cmd.CommandText = "UPDATE tblProduct SET .... ";
....
cmd.ExecuteNonQuery();
}
} |
Een FoodStoreObject gebruikt dan een bepaalde implementatie om de DB te gaan aanspreken:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class Klant : IFoodStoreObject
{
private IFoodStoreDataImpl _dataImpl;
private int _klantId;
private string _naam;
....
public void Save()
{
if( _klantId == -1 )
{
_dataImpl.InsertKlant ( ..... );
}
else
{
_dataImpl.UpdateKlant ( ..... );
}
}
....
} |
Een volgend probleem is:
Hoe en waar bepalen we welke data implementation er moet gebruikt worden
Kort gezegd: als de applicatie online is, moet de OnlineDataImpl (SQL Server DAL) gebruikt worden, anders de Access DAL.
Om dit te verwezenlijken zullen we dus een soort van 'ConnectionManager' nodig hebben.
Deze ConnectionManager zal dus verantwoordelijk zijn voor het bekijken van de 'netwerkstatus', en de gepaste actie(s) moeten ondernemen als deze status wijzigt.
Het is ook zo dat bepaalde acties offline moeten kunnen uitgevoerd worden, maar andere niet. Het is dan dus ook nodig dat er bepaalde controls v/d applicatie ge-enabled of gedisabled worden, naar gelang v/d connectie-status.
Als de connectiestatus veranderd, zullen er dus acties op Forms moeten gebeuren, en zal de te gebruiken FoodStoreDataImpl moeten veranderen.
Aangezien er slechts 1 ConnectionManager hoeft/mag zijn in de hele applicatie, kan die dus alleszins als een Singleton geimplementeerd worden.
Omdat een Form i/d applicatie moet kunnen reageren op een wijziging v/d connectionstate zal er dus op de een of andere manier communicatie moeten kunnen plaatsvinden tussen een Form en de ConnectionManager.
Om dit zo makkelijk mogelijk op te lossen zal ik gebruik maken van een (multicast) delegate als attribuut v/d ConnectionManager. Deze delegate zal getriggered worden als de connectionstate veranderd.
Om dit te verwezenlijken zal iedere form aan een bepaalde interface moeten voldoen; nl. een interface die ervoor zorgt dat de Form een eventhandler heeft die aan deze delegate kan gehangen worden.
Om dit even met UML te illustreren:

Aangezien .NET ook zoiets kent als 'Form inheritance', kan ik dus een Form maken die die ISmartClientForm implementeert, en waarvan al mijn andere forms dan overerven.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public abstract StandardForm : ISmartClientForm
{
public void StandardForm_OnCreate ( object sender, EventArgs e )
{
ConnectionManager.GetInstance().ConnectionStateChangedEvent +=
new ConnectionStateChangedEventHandler (OnConnectionStateChanged);
}
public void StandardForm_OnClose ( object sender, EventArgs e )
{
ConnectionManager.GetInstance().ConnectionStateChangedEvent -=
new ConnectionStateChangedEventHandler(OnConnectionStateChanged);
}
protected abstract OnConnectionStateChanged( object sender, ConnectionStateEventArgs e );
} |
In de OnConnectionStateChanged method van de inherited forms wordt er dan gekeken naar de ConnectionStateEventArgs, en aan de hand van dat argument worden dan bepaalde controls ge-enabled of gedisabled, etc...
De connectionmanager zal alleszins in een aparte thread moeten lopen en geregeld de netwerkstatus 'pollen'. Als die veranderd, dan zal de ConnectionStateChangedEventHandler moeten geraised worden.
De ConnectionStateChangedEventHandler is dan ook de geschikte plaats om de te gebruiken IFoodStoreDataImpl te bepalen.
De IFoodStoreDataImpl wordt echter het best gecreeërd op het moment dat we ze daadwerkelijk nodig hebben (in een IFoodStoreObject dus).
Het creeëren van de juiste IFoodStoreDataImpl doen we dus best in een IFoodStoreObject, en het bepalen welke Data Implementatie we nodig hebben, gebeurt dus best in de ConnectionManager; hiervoor kunnen we een 'Abstract Factory' gebruiken:

De ConnectionManager zal dus een object die de IFoodStoreDALFactory interface implementeerd bevatten.
De ConnectionManager zal dan ook een eigen EventHandler moeten hebben die bij zijn ConnectionStateChangedEvent geregistreerd is. Die eventhandler zal dan het juiste factory object creeëren:
code:
1
2
3
4
5
6
7
8
9
10
11
| public void OnConnectionStateChanged( object sender, ConnectionStateEventArgs e)
{
if( e.ConnectionStatus == ConnectionStatus.OnLine )
{
FoodStoreDALToUse = new OnlineDataImplFactory();
}
else
{
FoodStoreDALToUse = new OfflineDataImplFactory();
}
} |
In de IFoodStoreObject - classes kunnen we dan het volgende doen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public class Klant
{
IFoodStoreDataImpl _dataImpl;
....
public void Save()
{
_dataImpl = ConnectionManager.GetInstance().FoodStoreDALToUse.CreateDataImpl();
if( _klantId == -1 )
{
_dataImpl.InsertKlant ( .... );
}
else
{
_dataImpl.UpdateKlant ( .... );
}
}
} |
In principe heb ik nu dus al de 'grote lijnen' ontworpen om:
- een scheiding te maken tussen de DAL van de online en de offline store;
- de status op de forms te veranderen als de connectiestatus veranderd;
- de juiste DAL implementatie te kiezen als de connectiestatus veranderd.
Nu vraag ik me af wat jullie van deze 'architectuur' vinden. Vinden jullie dat er iets schort, dat er iets beter kan, dat er iets niet goed opgezet is, .... laat het weten. Het is mijn bedoeling om er meer over te leren.
Nu moet ik ook niet iets vinden om die 'Message Queueing te implementeren, maar daar moet ik nog eerst eens eea over lezen, en er eens goed over nadenken. Mijn eerste gedacht zou zijn, dat de messages die in de queue gezet moeten worden in de DAL v/d OfflineDataStore moeten gemaakt worden.
Ik moet ook de ConnectionManager nog verder uitwerken; zo moet de mogelijkheid nog voorzien worden om de ConnectieStatus te gaan detecteren, en bij een verandering de ConnectionStateChanged event te raisen.
Als jullie hier ideeën over hebben, post ze dan ook maar.
Hmmm, ik wist dat dit een lang topic kon worden, maar zo lang...
Hierbij nomineer ik dit topic dan ook maar voor langste topicstart van 2004
Alleszins bedankt aan iedereen die de moed vindt om dit topic te doorworstelen, en mij van input te voorzien.
https://fgheysels.github.io/

