[Alg/.NET] Architectuur smart client - Part II concrete case

Pagina: 1
Acties:
  • 196 views sinds 30-01-2008
  • Reageer

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
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:
Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/ERD.jpg
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):

Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/DAL.jpg
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. :P):
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:

Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/connectionman1.jpg

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:

Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/factory.jpg

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. :P

Hmmm, ik wist dat dit een lang topic kon worden, maar zo lang... :X
Hierbij nomineer ik dit topic dan ook maar voor langste topicstart van 2004 :o
Alleszins bedankt aan iedereen die de moed vindt om dit topic te doorworstelen, en mij van input te voorzien. :P

https://fgheysels.github.io/


Verwijderd

ff vraagje, je hebt nu vooral order's die aan 1 persoon worden gelinkt op 1 moment, dus er zullen nooit 2 medewerkers tegelijk een order bij een klant ophalen, en die gaan invoeren, en er zal ook niet snel een order worden opgehaald uit de database en vervolgens bij de klant gewijzigd...

ik weet niet of het bovenstaande verhaal duidelijk is, maar ik denk dat je 2 grote problemen vergeet. Neem bijvoorbeeld tweakers.net, nieuwssite, als er 2 personen een submit nemen en tegelijk offline een berichtmaken en vervolgens online zetten, dan heb je of 2 berichten of 1 van de berichten word opgeslagen. Of 2 editor's wijzigen een bericht en uploaden het allebij later..

natuurlijk kun je vergelijken en vragen of het geedit mag worden, en zorgen dat de gebruiker de verschillen ziet enz, maar genoeg gevallen waarbij het achteraf te laat kan zijn, omdat er niet rekening gehouden is met de nieuwe informatie, of dat de gebruiker te veel werk in de wijziging heeft gestopt.

moet je dan al in je busniness login dingen zoals kiezen bij verschil gaan stoppen? of hoe ga je rekening houden met sync? en hoe geef je dit door aan je database layer? zijn er ook dingen die alleen offline bijgehouden worden en niet online? zoals instellingen van de gebruiker in database? hoe geef je dit aan?

maja, ik programmeer ook niet zo lang, en heb pas met dit soort problemen te maken.

edit: hoop niet dat dit onder offtopic geblaat valt en je topic verpest ofzo, anders haal ik hem leeg of mag een mod hem verwijderen.

[ Voor 7% gewijzigd door Verwijderd op 06-06-2004 01:56 ]


  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 12-02 13:44
Interessant topic, ik ga dit zeker volgen. Ik kwam dit toevallig pas tegen om te zien of je op het netwerk aanwezig bent. De benadering is ietswat anders dan je vraag aangezien je kijkt of een server aanwezig is. Dit kan echter geen kwaad omdat je toch een server nodig hebt om je gegevens naartoe te transporteren.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private bool CheckNetwork()
{
System.Net.IPHostEntry hostInfo;
try
{
    // You can replace the following 2 lines,
    //if you can use a fixed servername
    string sServer = Environment.GetEnvironmentVariable("logonserver");
    //Remove leading "\\" because I only need the servername
    sServer = sServer.Remove(0, 2); 
    hostInfo = System.Net.Dns.GetHostByName(sServer);
    return true;        
}
catch
{
    return false;
}
}

[ Voor 12% gewijzigd door djluc op 06-06-2004 11:00 ]


  • MaxxRide
  • Registratie: April 2000
  • Laatst online: 09-01 10:13

MaxxRide

Surf's up

Lijkt me dat je met bovenstaande code en de conection manager kunt gaan "pollen" of er een verbinding is. Wellicth bestaat er in .Net ook een component die dit voor je kan doen en die ook een event voor je kan raisen.

Is best moelijk om dat met pollen te bouwen denk ik, je zou je voor kunnen stellen dat je om de 5 sec. polt. Echter de gebruiker trekt de PDA uit de cradle en drukt op verzenden, het systeem is in de veronderstelling dat er een verbinding is. Uiteraard is hier een simpele oplossing voor: voor het versturen kijken of er een verbinding is. Maar dit betekend wel dat de gebruiker op een knop heeft kunnen drukken die jij in offline modus niet beschikbaar wilt hebben.

Kortom, lijkt me dat je een dubbele check in moet bouwen als je op de knop drukt. Namelijk of je online bent.

Misschien liggen dit probleem en deze oplossing wat voor de hand, maar ik wilde ze toch even noemen :)

Verder vind ik de opzet erg mooi! Ik ben benieuwd naar het eindresultaat!

If you are not wiping out you are nog pushing enough...


  • Orphix
  • Registratie: Februari 2000
  • Niet online
Het synchroniseren en daarbij rekening houden met concurrency problemen, bijvoorbeeld met prioriteitsregels, zoals hierboven al werd verhaald kan een boekwerk op zichzelf mee gevuld worden. Ik denk dat daar honderd-en-een oplossingen voor zijn, afhankelijk van de context van de applicatie. Daar is, tot op zekere hoogte, niet direct een best-practice voor denk ik.

Maar toch iets wat ik me afvraag, en wat al in de eerste paragraaf genoemd worden: Je wilt dat wanneer de computer offline gaat de gegevens ge'cached' zijn op de computer. Ik begrijp alleen uit je verhaal dat er of de SQL server (online) of de Access database (offline) wordt benaderd. Wanneer de koerier z'n laptop uit de muur plugt is er natuurlijk geen mogelijkheid meer om die databases te synchronizeren. Hoe wil je dit oplossen?
Je zou bijvoorbeeld wanneer de gebruiker 'online' is beide DAL's kunnen gebruiken. Maar dan vang je niet het probleem op wanneer andere gebruikers op het netwerk gegevens op de SQL server aanpassen.

Verwijderd

Kan je niet gewoon gebruik maken van asynchroon messaging systeem zoals jms in java? Kwestie van dat je de opdracht voor het plaatsen van een nieuwe bestelling in zo'n queue plaatst zodat die doorgestuurd wordt wanneer er een verbinding mogelijk is.
Vervolgens heeft de ontvanger die bestelling gewoon te verwerken.
Of er netwerk is kan je gemakkelijk genoeg merken denk ik. Een onNetworkAvailable event ofzo.

Maargoed ik zou die acces databank laten vallen eigenlijk. Laat de client applicatie op de pda ofzo gewoon synchroniseren wanneer er netwerk is. Met asynchrone communicatie zou de applicatie net zo goed moeten kunnen blijven werken als er geen netwerk is. Ookal is die dan niet noodzakelijk up-to-date.

[ Voor 28% gewijzigd door Verwijderd op 06-06-2004 20:46 ]


  • MaxxRide
  • Registratie: April 2000
  • Laatst online: 09-01 10:13

MaxxRide

Surf's up

Het lijk me dat de client ook een up-to-date database moet hebben met klantgegevens danwel productgegevens danwel ordergegevens.

Het lijkt me ook dat zodra er een verbinding is de database synchroniseren. Eerst natuurlijk de nieuwe order plaatsen, dan b.v. incrementeel de access databsae syncen.

If you are not wiping out you are nog pushing enough...


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Verwijderd schreef op 06 juni 2004 @ 01:52:
ff vraagje, je hebt nu vooral order's die aan 1 persoon worden gelinkt op 1 moment, dus er zullen nooit 2 medewerkers tegelijk een order bij een klant ophalen, en die gaan invoeren, en er zal ook niet snel een order worden opgehaald uit de database en vervolgens bij de klant gewijzigd...
Je mag er wel vanuit gaan dat die medewerkers goed gecoördineert zijn, en dat een klant niet door 2 verschillende medewerkers op een dag bezocht wordt.
Stel dat iedere medewerker een ronde of een gebied heeft, met daarin een aantal klanten.

Mijn topic gaat dan ook niet echt over de DB architectuur, maar over de architectuur van de classes; hoe de DAL best gemodelleerd wordt, hoe die connection-manager , connectionDetection best gebeurt, en hoe de UI best geinformeerd wordt over een verandering in connectie-status, en hoe er dan best een andere DAL kan gekozen worden, etc...
Dit kan ik misschien wel gebruiken in de implementatie van de ConnectionDetector oid. ;)
Pinda schreef op 06 juni 2004 @ 13:19:
Lijkt me dat je met bovenstaande code en de conection manager kunt gaan "pollen" of er een verbinding is. Wellicth bestaat er in .Net ook een component die dit voor je kan doen en die ook een event voor je kan raisen.
Idd, op bepaalde tijdstippen zal er moeten gepolled worden.
Is best moelijk om dat met pollen te bouwen denk ik, je zou je voor kunnen stellen dat je om de 5 sec. polt. Echter de gebruiker trekt de PDA uit de cradle en drukt op verzenden, het systeem is in de veronderstelling dat er een verbinding is. Uiteraard is hier een simpele oplossing voor: voor het versturen kijken of er een verbinding is. Maar dit betekend wel dat de gebruiker op een knop heeft kunnen drukken die jij in offline modus niet beschikbaar wilt hebben.
Hmm, dan kan er evt een exception gethrowed worden, en kan er even gewacht worden en kan er opnieuw geprobeerd worden. Als het goed is, moet de connectionmanager dan wel gezien hebben dat de connectionstate veranderd is, en zou de andere DAL moeten gekozen worden.
Orphix schreef op 06 juni 2004 @ 15:32:
Maar toch iets wat ik me afvraag, en wat al in de eerste paragraaf genoemd worden: Je wilt dat wanneer de computer offline gaat de gegevens ge'cached' zijn op de computer. Ik begrijp alleen uit je verhaal dat er of de SQL server (online) of de Access database (offline) wordt benaderd. Wanneer de koerier z'n laptop uit de muur plugt is er natuurlijk geen mogelijkheid meer om die databases te synchronizeren. Hoe wil je dit oplossen?
Je zou bijvoorbeeld wanneer de gebruiker 'online' is beide DAL's kunnen gebruiken. Maar dan vang je niet het probleem op wanneer andere gebruikers op het netwerk gegevens op de SQL server aanpassen.
De medewerker (koerier), zal toch wel voordat hij z'n computer offline haalt, de gegevens zelf moeten downloaden. Stel bv. dat hij zijn 'rondegegevens' oid afhaalt/download voordat hij op z'n ronde vertrekt.
Verwijderd schreef op 06 juni 2004 @ 20:44:
Kan je niet gewoon gebruik maken van asynchroon messaging systeem zoals jms in java? Kwestie van dat je de opdracht voor het plaatsen van een nieuwe bestelling in zo'n queue plaatst zodat die doorgestuurd wordt wanneer er een verbinding mogelijk is.
Vervolgens heeft de ontvanger die bestelling gewoon te verwerken.
Of er netwerk is kan je gemakkelijk genoeg merken denk ik. Een onNetworkAvailable event ofzo.
Dat is idd de bedoeling.
Die offline DB is toch ook nodig om gegevens te kunnen consulteren als de applicatie niet online is. In dat geval zou ik de bewerkingen idd mbhv een messaging systeem laten verwerken. (Van zodra de app online is, de messages versturen en laten verwerken op de centrale server).
Pinda schreef op 06 juni 2004 @ 13:19:
Verder vind ik de opzet erg mooi! Ik ben benieuwd naar het eindresultaat!
Jah, ik ook. Hopelijk vind ik de moed om ermee door te gaan, en het af te werken. :P
Al denk ik wel dat ik er nog een tijdje zal mee zoet zijn, zeker nu het weer wat mooier wordt. :P

Trouwens, niemand die opmerkingen heeft over die architectuur v/d klassen ? :?

[ Voor 83% gewijzigd door whoami op 06-06-2004 22:54 ]

https://fgheysels.github.io/


Verwijderd

Mwa ik vraag me gewoon af wat er dan in die lokale database precies moet zitten. Als het enkel lokale orders zijn dan vind ik het niet de moeite.
Als de hele catalogus er ook in moet zitten dan vraag ik me haast af of dat niet te groot kan worden voor zo'n pda. Maargoed akkoord je zal die gegevens toch ergens moeten bewaren en liefst niet in het werkgeheugen.

Uiteindelijk is het wel aan te raden dat je op beide dezelfde entiteiten kan gebruiken ongeacht van waar die dingen nu precies opgeslagen zijn.

Verwijderd

Wordt het uitgeven van unieke sleutels ook afgehandeld door de klassen?
Ik bedoel als je gaat syncen wil je toch niet tot problemen komen met botsingen ivm met dubbele sleutels etc.
Kan me voorstellen dat je een soort sleutelpools aanmaakt, waaruit iedere client een range sleutels (per tabel) kan halen, het hoeven natuurlijk niet persé unieke record sleutels te zijn, denk bv alleen al aan ordernummers, factuurnummers etc. Deze zouden dan door een centrale tabel in de centrale database uitgegeven moeten worden ofzo. Ik denk dat het wel iets is om in de klassen op te nemen dat ze dit soort zaken afhandelen.

[ Voor 11% gewijzigd door Verwijderd op 07-06-2004 00:31 ]


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Verwijderd schreef op 07 juni 2004 @ 00:31:
Wordt het uitgeven van unieke sleutels ook afgehandeld door de klassen?
Ik bedoel als je gaat syncen wil je toch niet tot problemen komen met botsingen ivm met dubbele sleutels etc.
Idd, ik had hier zelf gedacht om GUID's te gebruiken.

https://fgheysels.github.io/


Verwijderd

whoami schreef op 07 juni 2004 @ 08:30:
[...]
Idd, ik had hier zelf gedacht om GUID's te gebruiken.
Da's mooi voor unieke records sleutels, echter voor ordernr, factuurnr's etc kan je natuurlijk niet altijd een GUID gebruiken. En toch heb je hier ook een mechanisme/algortihme voor nodig om deze uniek te houden.

  • MaxxRide
  • Registratie: April 2000
  • Laatst online: 09-01 10:13

MaxxRide

Surf's up

Je kunt natuurlijk een tijdelijke sleutel in de offline db hebben, op het moment dat je de order dan naar de online DB stuurt laat je daarop een "echte" sleutel maken. (Helemaal als je met async messaging werkt zou dit geen probleem hoeven opleveren volgens mij).

Dus b.v. een order wordt geplaatst, voor de duidelijkheid van de koerier wordt deze in de offline DB geplaatst en in de message queue. De koerier kan nu checken etc. Wanneer de PDA wordt vastgekoppeld wordt niet de DB gesynced naar de online DB maar worden de messages overgestuurd en wordt daarna de offline database opnieuw gesynced. Dan heb je het probleem van de sleutels niet.

If you are not wiping out you are nog pushing enough...


  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 12-02 13:44
Kort gezegd: als de applicatie online is, moet de OnlineDataImpl (SQL Server DAL) gebruikt worden, anders de Access DAL.
Is het niet veel eenvoudiger om gewoon altijd alles wat je op de PDA doet te laten verlopen via de Acces database. Zodra je verbinding met het netwerk hebt ga je om de x seconden pollen of er dingen in de wachtrij staan en ga je deze versturen naar de server. Dan hoef je in je cliënt app geen rekening te houden met de server, en eventuele verschillen. Je centraliseerd dan alle communicatie met de server.

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
djluc schreef op 07 juni 2004 @ 14:32:
[...]
Is het niet veel eenvoudiger om gewoon altijd alles wat je op de PDA doet te laten verlopen via de Acces database. Zodra je verbinding met het netwerk hebt ga je om de x seconden pollen of er dingen in de wachtrij staan en ga je deze versturen naar de server. Dan hoef je in je cliënt app geen rekening te houden met de server, en eventuele verschillen. Je centraliseerd dan alle communicatie met de server.
Dan zal je offline DB zowiezo nooit over de recentste informatie beschikken.
Stel dat medewerker A opeens een klant moet bezoeken die normaal door medewerker B bezocht wordt, dan heb je die info niet.
Zowiezo moet je dan toch informatie van de centrale DB overhevelen naar de offline DB.

https://fgheysels.github.io/


  • djluc
  • Registratie: Oktober 2002
  • Laatst online: 12-02 13:44
whoami schreef op 07 juni 2004 @ 14:35:[...]Dan zal je offline DB zowiezo nooit over de recentste informatie beschikken.
Stel dat medewerker A opeens een klant moet bezoeken die normaal door medewerker B bezocht wordt, dan heb je die info niet.
Zowiezo moet je dan toch informatie van de centrale DB overhevelen naar de offline DB.
Na de poll heb je die info toch wel? Dan heb je hoogstens enkele seconden geen recente data, maar je kunt je offline applicatie vervolgens eenvoudiger ontwikkelen omdat je in de gehele offline app geen rekening meer hoeft te houden met verschillende servers. Alleen 1 stukje code zorgt voor het pollen en updaten van de gegevens. Dat heeft dus verder niets te maken met verschillende klanten o.i.d.

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Bij iedere poll je gegevens updaten als de app online is, is ook geen goed idee. 8)7

Als de applicatie offline is, zal er ook geen rekening met verschillende servers moeten gehouden worden. Dan worden de gegevens gehaald uit de offline app (die de gegevens bevat doordat de medewerker manueel heeft aangegeven welke gegevens hij ook offline wil gebruiken). Als er mutaties op die gegevens moeten gedaan worden, dan worden die mbhv message queueing naar de centrale server overgezet.

De medewerker gaat dus voordat hij 'offline' gaat, gegevens over z'n 'klantenronde' moeten downloaden.

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Ik heb er nog even aan verdergewerkt. :P

Ik heb besloten om de signatures van de methods in IFoodStoreDataImpl te veranderen; in plaats van alle attributen apart door te geven, denk ik dat het beter is om het IFoodStoreObject mee te geven. Dit omdat het van pas zal komen bij het maken van de Messages die verstuur worden naar de messagequeue als de applicatie offline is. (Meer daarover straks).
Die aanpak is misschien wel niet zo 'puristisch' als het gaat om het Bridge design pattern, omdat je dan toch min of meer een 'hardere' koppeling maakt tussen de Data-implementation en de data-abstraction, maar ik denk dat dit in dit concreet geval geen kwaad kan.
(Indien er iemand commentaar heeft op de structuur van de data-classes, feel free om het te zeggen).

Ik heb ondertussen eens met message-queueing in .NET gespeeld, en gekeken wat de mogelijkheden zijn.
Ik zal een QueueManager nodig hebben waarin de messages 'gequeued' worden als de applicatie offline is. Daarnaast zal ik natuurlijk ook Messages nodig hebben om te versturen.
Als de applicatie online is, zullen die messages moeten geprocessed worden. Dit zal moeten gebeuren door een 'executor' die de queue afloopt en ieder message laat verwerken.

Visueel voorgesteld:

Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/queuespul.jpg

Een message moet kunnen verstuurd en verwerkt worden, vandaar de interface IMessage. Een FoodStoreMessage implementeert die IMessage en bevat een reference naar een IFoodStorePayload die op zijn beurt een IFoodStoreObject bevat, en ook een FoodStoreOperation, zodat je weet welke method er moet uitgevoerd worden.

Om dit even in pseudo-code voor te stellen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class FoodStoreMessage
{
    private FoodStorePayLoad      _payLoad;

    public FoodStoreMessage( IFoodStoreObject fo, FoodStoreOperation fop )
    {
         _payLoad = new FoodStorePayLoad (fo, fop);
    }

    public void ProcessMessage ()
    {
         switch( _payLoad.Operation )
         {
              case FoodStoreOperation.Save : _payLoad.FoodStoreObj.Save();
                                              break;
              case FoodStoreOperation.Delete : _payLoad.FoodStoreObj.Delete();
                                               break;
         }
    }

    public void Send()
    {
           ....
           // verzend hier deze message naar de queue-manager
    }
}


Dit verklaart ook al direct waarom ik nu een IFoodStoreObject wil meegeven aan de data-implementation classes ipv ieder attribuut apart.
Ik kan nu namelijk in de offline-datastorage implementation makkelijk een Message maken en versturen:

code:
1
2
3
4
5
6
7
8
public void InsertKlant ( Klant k )
{
    // insert de klant in de offline storage
    ...
    // maak een message en verstuur het.
    FoodStoreMessage m = new FoodStoreMessage (k, FoodStoreOperation.Save);
    m.Send();
}


De QueueManager class zal een singleton worden, aangezien er slechts 1 queuemanager moet zijn in de applicatie.
De instance van de QueueManager zal alleszins moeten gebruikt worden in de Send method van de Message zelf, aangezien het de QueueManager is in .NET die over een Send message beschikt, en niet het Message object. (Zie de .NET SDK voor meer info).

De executor zal ook een singleton zijn. In de OnConnectionChanged event van de ConnectionManager zal deze -afhankelijk van de connectionstate- gestart of gestopt worden.
De executor zal alle FoodStoreMessages van de Queue halen, en deze uitvoeren.

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Is er niemand die kritiek heeft op deze opzet?
Niemand die bv. vind dat ik toch beter geen FoodStoreObject (zoals Klant, Product, etc...) doorgeef aan de database-functies van de DataImplementation-classes en toch beter ieder argument apart doorgeef als parameter?

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Ik heb hier ondertussen nog wat verder aan geprutst, en het uiteindelijke design is dit geworden:

Afbeeldingslocatie: http://users.pandora.be/fgzone/pics/foodstorecompl.jpg
clickable


Ik heb het 'project' niet volledig geimplementeerd; met name de DAL classes en de UI zelf heb ik niet echt uitgewerkt.
Ik heb wel een aantal DAL classes (Customer oa) uitgewerkt, en een aantal testformpjes gemaakt. (Misschien zal ik het ooit nog eens volledig uitwerken; het ontwerpen en het schrijven van de 'interessante' code was dan ook leuker dan het volledig uitwerken van het concept. :P)

De data-implementation en FoodStoreObj objecten heb ik ongeveer zo uitgewerkt zoals ik in eerdere posts hier beschreven heb.
Ik heb wel, naast de IFoodStoreObject interface ook nog een abstracte class 'AbstractFoodStoreObj' die de IFoodStoreObject interface implementeert gemaakt. Alle uiteindelijke 'foodstore objecten (Customer, Category, .... ) inheriten uiteindelijk van die abstracte class.
Het nut van die abstracte class is eenvoudig: nu moet ik niet meer in iedere member-method van de concrete foodstoreobjecten (customer, category, ... ) het juiste data-implementation object gaan creeëren. Dit doe ik in de abstracte class:
code:
1
2
3
4
5
6
7
8
9
10
[Serializable]
public abstract class AbstractFoodStoreObj : IFoodStoreObj
{

    protected   IFoodStoreDataImpl      _dataImpl;
        
    public virtual void     Save()
    {
        _dataImpl = FoodStoreConnectionManager.Instance.FoodStoreDataImplFactory.CreateDataImplementation();
    }


Hmm, die abstracte class staat niet op m'n UML. :P

De OnlineData Implementation gaat dus de objecten direct in de SQL Server databank gaan persisten.
De Offline Data Implementation zou eigenlijk de objecten in de lokale datastore (Access) moeten bewaren, en een Message maken en dit dmv Message Queueing verzenden.
Gemakshalve (en omdat DAL code schrijven saai is) heb ik me in de offline - datastore enkel beperkt tot het 'message queue-en'.

Het message - queue'en zelf gaat als volgt in z'n werk:

De FoodStoreMessageQueue class is een singleton-class en heeft een method 'CreateQueue' die aangeroepen wordt in de constructor van de 'Main Form' in de UI.
Die CreateQueue gaat een private message-queue aanmaken op het systeem waar alle messages uiteindelijk in terecht komen.

Zoals je op het classdiagram kunt zien, heeft een 'FoodstoreMessage' een member method 'ProcessMessage', en bevat het een 'payload'.
De payload is gewoon het object zelf, en de operatie die nadien zal moeten uitgevoerd worden.
In de offline data implementation kom je dus bv deze code tegen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
[Serializable]
    public class OfflineDataImpl : IFoodStoreDataImpl
    {       

        public void InsertCustomer(Customer c)
        {
        
            FoodStoreMessage msg = new FoodStoreMessage(
                                           new FoodStorePayLoad(c, 
                                                                FoodStorePayLoadOperation.Save));
            FoodStoreQueueManager.Instance.EnqueueMessage (msg);
        }


Zolang de applicatie 'offline' is, worden er message's in de queue gezet.
Vanaf dat de connectiestatus veranderd, wordt de OnConnectionStateChanged event van de ConnectionManager getriggerd.
Dit event gaat er onder meer voor zorgen dat het juiste 'DataImplFactory' object gecreeërd wordt, en als de applicatie online gaat, zal de 'FoodStoreMessageExector' ook gestart worden. Deze laatste gaat ervoor zorgen dat alle messages van de queue gehaald en geprocessed worden:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected override void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
{
            
    if( e.CurrentState == ConnectionState.Online )
    {
        _dataImplFactory = new OnlineDataImplFactory ();
                
        FoodStoreMessageExecutor.Instance.StartExecutor ();
    }
    else
    {
        _dataImplFactory = new OfflineDataImplFactory ();
    }
}


De executor wordt op een aparte thread uitgevoerd:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void StartExecutor ()
{
    _workerThread = new Thread(new ThreadStart(this.ProcessMessages));
    _workerThread.Start ();
}
                
private void ProcessMessages ()
{
    IMessage                message;
        
    foreach( string id in FoodStoreQueueManager.Instance.GetAllMessageIds () )
    {
        message = FoodStoreQueueManager.Instance.DequeueMessage (id);
                
        message.ProcessMessage ();
    }
}


Zoals je kan zien gebruik ik hier (nog) geen Transactional Message Queueing.

De ProcessMessage method die aangeroepen wordt, gaat gewoon de juiste method van de juiste data-implementation aanroepen.
code:
1
2
3
4
5
6
7
8
9
10
public void ProcessMessage()
{
    switch( _payLoad.Operation )
    {
        case FoodStorePayLoadOperation.Save   : _payLoad.FoodStoreObj.Save ();
                                                    break;
        case FoodStorePayLoadOperation.Delete : _payLoad.FoodStoreObj.Delete ();
                                                    break;
    }
}


Aangezien de User Interface ook moet kunnen reageren op een verandering van de connectionstate, heb ik dus ook een interface 'ISmartClientForm' gemaakt, die een method bevat die voldoet aan de signature die de ConnectionStateChanged event van de ConnectionManager verwacht.
Alle forms van de applicatie erven dan over van een 'basis-form' die de ISmartClientForm interface implementeerd. Deze basisform gaat niets anders doen dan de OnConnectionStateChanged method te gaan registreren en te gaan verwijderen bij de ConnectionStateChanged event v/d ConnectionManager:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public AbstractFoodstoreForm()
{
            
    InitializeComponent();

    FoodStoreConnectionManager.Instance.ConnectionChangedEvent +=
                            new ConnectionStateChangedEventHandler(this.OnConnectionChanged);
}

private void AbstractFoodstoreForm_Closed(object sender, System.EventArgs e)
{
    FoodStoreConnectionManager.Instance.ConnectionChangedEvent -=
                            new ConnectionStateChangedEventHandler(this.OnConnectionChanged);
}


Forms die dus inheriten van deze form kunnen dus makkelijk reageren en actie ondernemen bij een verandering in de connectionstate:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public override void OnConnectionChanged(object sender, MessageQueueFramework.ConnectionStateChangedEventArgs e)
{
    if( e.CurrentState == MessageQueueFramework.ConnectionState.Online )
    {
        this.StatusBar.Panels[0].Text = "Online";
                
        mnuProductMgt.Enabled = true;
    }
    else
    {
        this.StatusBar.Panels[0].Text = "Offline";
                
        mnuProductMgt.Enabled = false;
    }
}


Zoals altijd , is commentaar natuurlijk welkom.

[ Voor 3% gewijzigd door whoami op 26-06-2004 16:40 ]

https://fgheysels.github.io/


  • Scharnout
  • Registratie: November 2000
  • Laatst online: 12-12-2025

Scharnout

Meuk

Ik ben helaas verre van gevorderd, maar het eerste wat ik me afvroeg is: Waarom twee verschillende databases gebruiken? Je kan MSDE gewoon mee laten instaleren met je client app. Of gaat het hier om de uitdaging van juist wel 2 verschillende datadragers?

And Bob's your uncle ...


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
Je kan inderdaad stellen dat het 'part of the job' is. :P

https://fgheysels.github.io/


  • staefke
  • Registratie: December 2003
  • Laatst online: 10-02 21:38
Die DAL code is uiteraard saai, maar hiervoor kun je generatoren of OR mappers gebruiken. Zelf ben ik een beetje aan het prutsen geweest met Gentle.Net (http://sourceforge.net/projects/gopf/) dat erg handig werkt. Misschien een overweging waard :)

zie ook http://www.mertner.com/co...tle/3.1+-+A+Basic+Example voor een voorbeeld hoe een eenvoudige class ff naar database gemapped wordt in een paar stappen...

duh ?


  • PhoneTech
  • Registratie: Mei 2000
  • Laatst online: 19:34
Hmm...Leuk topic.

Ik heb een implementatie gemaakt waar je 1 abstracte data provider hebt, en dan meerdere implementaties hiervan dynamisch kan inladen door middel van reflection. De keuze van de specifieke database provider staat bij mij in de web.config, maar dit kan je ook door middel van een connectiondetector doen bij de static Instance:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class DataProvider 
    {
        private const string ProviderType = "data";     
        
        public static DataProvider Instance()
        {
            string  strCacheKey = ProviderType + "provider";
            // Use the cache because the reflection used later is expensive
            ConstructorInfo objConstructor = (ConstructorInfo)DataCache.GetCache(strCacheKey);          
            if ( objConstructor == null ) 
            {
                // get { the name of the provider
                ProviderConfiguration objProviderConfiguration = ProviderConfiguration.GetProviderConfiguration(ProviderType);

                // The assembly should be in \bin or GAC, so we simply need to get an instance of the type
                try 
                {
                    // get { the typename of the Core DataProvider from web.config                 
                    string strTypeName = ((Provider)objProviderConfiguration.Providers[objProviderConfiguration.DefaultProvider]).Type;

                    // Use reflection to store the constructor of the class that implements DataProvider
                    Type t = Type.GetType(strTypeName, true);
                    objConstructor = t.GetConstructor(System.Type.EmptyTypes);

                    // Insert the type into the cache
                    DataCache.SetCache(strCacheKey, objConstructor);

                } 
                catch (Exception e)
                {
                    // Could not load the provider - this is likely due to binary compatibility issues 
                    throw e;
                }
            }
            return (DataProvider)objConstructor.Invoke(null);
        }

        public abstract string  ExecuteScript(string  SQL); 
        public abstract string  ExecuteScript(string  Script, bool UseTransactions);
    }


Vervolgens zitten de zelfde implementaties in de verschillende data probivers maar dan met de implementatie erbij

  • Scharnout
  • Registratie: November 2000
  • Laatst online: 12-12-2025

Scharnout

Meuk

Toevallig vandaag aan begonnen met lezen. Misschien heb je er wat aan:

http://msdn.microsoft.com...en-us/dnpag/html/scag.asp

And Bob's your uncle ...


  • whoami
  • Registratie: December 2000
  • Laatst online: 17:04
:P
Ik ben het ook aan het lezen. ;)

https://fgheysels.github.io/


Verwijderd

Scharnout schreef op 27 juni 2004 @ 15:01:
Ik ben helaas verre van gevorderd, maar het eerste wat ik me afvroeg is: Waarom twee verschillende databases gebruiken? Je kan MSDE gewoon mee laten instaleren met je client app. Of gaat het hier om de uitdaging van juist wel 2 verschillende datadragers?
Je hebt ook niet altijd de mogelijkheid om je "eigen" database te gebruiken, vaak zit je bij omgevingen waar bepaalt is dat alle database op b.v. Oracle of op MSSql moeten draaien, wij komen dit ook regelmatig tegen en onze applicatie's zijn "native" tegen Progress geschreven. Vaak kan je hier wel weer tussenlagen tussen hangen maar dat is toch niet altijd 100%.
Pagina: 1