Design

Indata till designen är kravspecen som den uttrycks i de modeller som skapades under analys. Syftet med design är att tänka igenom hela det avsnitt av koden som ska konstrueras och att skapa en tydlig bild av hur koden ska struktureras. De klasser, metoder osv som finns i designmodellen är tänkta att finnas i koden också.

Design är en väldigt viktig aktivitet för att få en genomtänkt kod. Det är också en relativt svår aktivitet som mycket handlar om erfarenhet. Här är centrala delar av "designkunskaperna" sammanfattade i några viktiga principer och designmönster.

Just eftersom design är så viktig måste vi komma ihåg att det inte ska vara mer än "en stunds eftertanke för att komma mer rätt från början". Designen kommer aldrig att bli så bra att kodningen blir en rent mekanisk översättning av diagrammen till källkodsfiler. Mycket av det som finns i designmodellen kommer att ändras vid kodning. Det är knappast meningsfullt att ägna mer än en halv dag åt design i en enveckas iteration.

Designens grundprinciper

Det här är väldigt långt ifrån en fullständig kurs i objektorienterad design, vi tar bara en kort titt på några av de allra viktigaste principerna. Först av allt, syftet:

Det ska vara lätt att ändra programmets beteende och att utöka dess funktionalitet, detta ska kunna göras utan att behöva ändra i befintlig kod.

Når vi dit kan programmet leva länge utan att koden degenererar till en ounderhållbar gröt som ingen vågar ändra i.

Definition - Implementation

Definitionen är det som andra objekt kan se, tex namn på klasser och interface och signaturer för publika metoder. Implementationen är till exempel privata metoder och koden som publika metoder innehåller. Ingenting får vara beroende av implementationen, det enda som ska användas av andra objekt är definitionen. På så sätt är det möjligt att ändra i implementationen utan att påverka någon annan kod än den som ändras. Betrakta detta lilla exempel från collection-API:et i paketet java.util:
List är ett interface som innehåller definitioner av metoder för en lista. ArrayList och LinkedList är klasser som innehåller helt olika implementationer av en lista, dvs helt olika kod. Låt oss nu anta att något objekt använder en lista på det här sättet:
List list = new ArrayList();
list.add(obj);
list.get(2);
//en massa andra operationer på listan.
Helt plötsligt kommer vi på att det var dåligt att använda ArrayList, vi borde ha en LinkedList i stället. Det enda som behöver göras är att ändra första raden till List list = new LinkedList(). Detta innebär att en helt annan kod (implementationen) kommer att exekveras men tack vara att vi bara refererade till interfacet List (definitionen) krävde det nästan ingen alls förändring av koden! Interface borde användas på detta sätt i mycket större utsträckning än vad som är fallet!

Detta resonemang gäller även på andra nivåer, vi kan till exempel ha ett paket med bara en publik klass (en sådan klass brukar kallas fasad). Dess publika gränssnitt utgör då paketets definition och alla andra klasser i paketet utgör dess implementation.

Det är ofta ofarligt att ändra en implementation men livsfarligt att ändra en definition eftersom det kan finnas väldigt många beroenden av den.

Inkapsling

Enligt resonemanget ovan är viktigt att ha en stabil definition. Exakt vad är det då som utgör definitionen av till exempel en klass? Jo, det är allting som någonting utanför klassen kan se, dvs klassens namn samt signaturen av dess publika variabler, metoder samt inre klasser och interface (dvs dess publika gränssnitt). Desto mindre denna definition är desto lättare är det hålla den stabil. Alltså bör klassen ha få publika metoder och helst inga publika variabler alls.

Hur är det då med klasser, metoder osv som är protected eller paketprivata? Protected behöver väldigt sällan användas så det hoppar vi över. Paketprivat åtkomst är däremot mycket vanlig. Den är mycket närmre besläktad med privat åtkomst än med publik. Låt oss anta att vi ska utveckla en viss begränsad funktionalitet, till exempel hantering av ett bankkonto (inte av ett helt banksystem utan bara av ett konto). Vi inser att det finns olika typer av konton, olika räntor osv varför det inte räcker med en enda klass. Vi utvecklar då ett kontopaket istället för en kontoklass. Detta kontopaket blir en mycket väl sammanhållen enhet med ett tydligt publikt gränssnitt. För tydlighets skull råkar paketet innehålla mer än en klasser. Att dessa klasser anropar varandras metoder (paketprivat åtkomst) är dock ett relativt ofarligt beroende eftersom de är en så väl sammanhållen enhet. Detta resonemang håller inte alltid till fullo eftersom ett pakets innehåll inte alltid är så väl sammanhållet. Det viktigaste är dock att begränsa den publika åtkomsten även om privat i och för sig är bättre än paketprivat. Protected undviker vi om vi inte vet mycket väl varför vi ska ha det.

En annan aspekt av inkapsling är att variabler helst alltid ska vara privata, ovanstående resonemang gäller främst allt utom variabler. Anledningen till att variabler ska vara privata är att om de accessas uteslutande via metoder i klassen har vi full koll på hur de accessas. Vi kan till exempel i metoderna utföra omvandlingar mellan enheter, utföra säkerhetskoller, göra typkonverteringar osv. Att accessa variabler via metoder gör också koden robustare. Om vi till exempel vill byta typ på variabeln behöver vi inte byta typ på det publika gränssnittet i metoden som accessar den. Vi har också möjlighet att till exempel synkronisera access av variabeln om det helt plötsligt skulle bli nödvändigt.

Hög sammanhållning (high cohesion)

En klass ska ha väldefinierad kunskap och en väldefinierad uppgift. En klass Anställd ska till exempel inte innehålla en massa information om företaget den anställde jobbar på. Den kan i stället ha en referens till en klass Företag som innehåller den informationen.

Vidare ska klasser bara innehålla metoder som är relaterade till det klassen representerar. Klassen Anställd ska till exempel innehålla metoder som hanterar dess namn och adress och vilket företag den jobbar på, medans klassen Företag ska innehålla metoder som hanterar till exempel lönehantering för den anställde.

En slutsats av detta blir att det är viktigt att dela upp klasser. Det är bara alltför vanligt att program innehåller för få och för stora klasser som har för mycket och för oklar kod som snart blir grötig och svårunderhållen.

Låg koppling (low coupling)

Klasser ska ha litet beroende av andra klasser, dvs de ska inte i onödan anropa metoder på andra klasser eftersom koden i dem då påverkas av ändringar av koden i de anropade klasserna. Olika typer av beroenden är dock olika farliga. Det är till exempel ofarligt att använda klasserna i paketen java.* som levereras med J2SE eftersom dessa klasser är extremt stabila. Mer generellt är ett beroende ofarligare ju stabilare den anropade klassen är. Vidare är det ofarligare med ett beroende av en klass i samma paket än av en klass i ett annat paket eftersom vi har större koll på klassen i samma paket. Desto större koll vi har på klassen vi är beroende av detso ofarligare är beroendet.

Några fler tips (patterns)

Kanske inte direkt designens grundprinciper men vanliga lösningar på vanliga problem

Controller

Problem

Vart ska ett system event (anropen från aktören i SSD) ske? Vem ska hantera dem?

Lösning

Klasser som har detta ansvar kallas controller.

Vi kommer huvudsalkigen att använda klasser (eller metoder i klasser) som representerar scenarion i ett use case. Det är bra (för tydlighets skull om namnet på use case:t finns med i namnet på metoden (om det är en metod per use case/scenario) eller klassen (om det är en klass per use case/scenario). Denna variant kallas use case controller.

En annan lösning är att inte associera controllern med scenarion utan med en del av programmet (tex ett paket). Klassen hanterar då all användning av subsystemet oavsett scenario. Detta kallas en facade controller.

Observera att en controller aldrig ska vara en del av användarggränssnittet.

Creator

Problem

Vem ska skapa nya instanser av klasser?

Lösning

Det är lämpligt att A skapar instanser av B om

Arkitektur

Arkitekturen är systemets "skelett", hur det ser ut i stora drag. Arkitekturen talar inte om hur problem löses, snarare att de kan lösas och var de löses. Den växer egentligen fram under iterationerna i eleboration. I vårt fall är dock ganska mycket givet: J2EE, uPortal, EJB:er, PostgreSQL, JBoss, SPARC-Solaris samt hårdvaran. Jag har dessutom tagit mig friheten att på egen hand gå vidare med arkitekturen. Vad är detta?!? Diktatur! Vattenfallsprocess!! Möjligen det första, men projektet är stort nog ändå. Den lösning jag skissar är på intet sätt kontroversiell, det är en typisk J2EE-struktur. Jag har fråntagit er glädjen att läsa en massa om J2EE best practises och upptäcka en massa tjusiga mönster att välja bland. Problemet är att det tar minst ett år att sjunka in ordentligt i J2EE och dessutom hade ni förmodligen kommit fram till en likartad lösning ändå. Vattenfallsprocess är det däremot inte. En process lever inte för sin egen skull. Efter att ha gjort en massa olika projekt med samma teknologi får man helt naturligt en känsla för en lämplig arkitektur. Då ska man naturligtvis inte vara rädd för att använda den trots att processen är iterativ. Däremot måste man vara öppen för förändringar och nya upptäckter. Jag har inte gjort en färdig kalender för att kolla att det fungerar, upptäcker vi om några veckor att arkitekturen inte håller måste vi vara beredda att ändra den.

Skikt

Det första steget är att dela upp systemet i skikt.  Skikten blir alltid ungefär desamma när det handlar om webapplikationer men det finns många olika namn på dem. Vi kallar dem Client (browsern), Presentation (där vyerna konstrueras, i vårt fall utgörs det av uPortal), Business (där all affärslogik hanteras, i vårt fall EJB:erna), Integration (mappning mellan den objektorienterade modellen i Business och relationsdatabasen i Resource) samt Resource (persistent lagring,ivårt fall Postgres-databasen):

Observera att det bara finns beroenden av djupare liggande skikt.

Presentation

Det första skiktet vi tittar in i är presentation, dvs kanalen i uPortal. Här bygger vi ett ramverk som kommer att göra det lätt att utöka och ändra funktionaliteten. Idén är att själva kanalklassen, UCalChannel, inte ska innehålla någon flödeskontroll eller någon annan logik. Det är i klasser som implementerar Action som själva arbetet utförs. UCalChannel ber ActionFactory om en Action-klass som kan utföra det arbete som krävs när en viss vy visas och användaren har klickat på en viss knapp. Om till exempel vyn boka möte visas ovh användaren klickar på OK kanske en Action BookMeetingAction returneras som hanterar bokandet av ett möte. ActionFactory väljer rätt Action mha en XML-fil som kopplar vy+knapp till rätt Action-klass. När en Action skapas skapar den en BusinessDelegate som är en klass som innehåller alla anrop till business-skiktet (alltså EJB:erna) för ett visst use case. På det viset har all kunskap om business-skiktet isolerats i en klass. En Action har en metod  doAction som utför själva arbetet. Den returnerar en Dispatcher-klass som ska hantera visandet av nästa vy. Dispatcher kan dels tala om för UCalChannel vad nästa vy heter, dels skapa den (i metoden renderXML). I diagrammet ser det ut som om namnet på Dispatcher-klassen är hårkodat i Action-klassen. Det är inte nödvändigt, det skulle lika gärna kunna stå i ActionFactorys XML-fil.

Det tjusiga med denna lösning är att det enda som behöver göras för att lägga till ny funktionalitet är att skriva en ny Action, en ny Dispatcher och eventuellt en ny BusinessDelegate samt att lägga till en rad i XML-filen. Den befintliga koden påverkas inte alls!! Om någon funktionalitet ska ändras är risken att påverka något annat än det som ändras minimal eftersom allting är isolerat i en egen Action och en egen Dispatcher.

Det var presentation i stort, nu tar vi en titt inuti några av klasserna:

  1. ActionFactory
    Detta är en singleton vilket innebär att det bara finns ett objekt av den. Den ser ut ungefär så här:
    class ActionFactory {
        private static final ActionFactory instance = new ActionFactory();

        private ActionFactory() {}

        public getInstance() {
            return instance();
        }
        ...
    Dessutom har den en metod getAction som läser i XML-filen och returnerar ett objekt av rätt Action. Om Action-objekten är tillståndslösa, dvs inte har några instansvariabler (vilket de absolut inte bör ha) behöver ActionFactory inte skapa nya objekt varje gång utan kan cacha dem i en Map. På  så sätt behövs det bara skapas ett objekt av varje Action.
  2. UCalChannel
    Den kod som ingår i ramverkat är något i den här stilen:
    ActionFactory actionFactory = ActionFactory.getInstance();
    Dispatcher dispatcher;
    ScreenName screenName;

    public void setRuntimeData(ChannelRuntimeData data) {
        dispatcher = actionFactory.getAction(screenName, userAction).doAction(data);
        screenName = dispatcher.getScreenName();
    }

    public void renderXML(ContentHandler out) {
        dispatcher.renderXML(out);
    }
  3. ScreenName
    Detta är en uppräkningsbar typ som finns för att vi inte ska behöva skriva stränger som utgör namnen på vyerna och riskera krångel pga felstavningar. Överallt där namnet på en vy ska anges ska vi alltså använda konstanter från ScreenName. En uppräkningsbar typ i Java ser ut så här:
    class ScreenName {
        static final ScreenName MAIN_SCREEN         = new ScreenName();
        static final ScreenName BOOK_MEETING_SCREEN = new ScreenName();
        //names for all screens.

        private ScreenName(){}
    }


Business

I affärslogikskiktet behöver vi inte skriva något ramverk eftersom EJB i sig är ett utordentligt ramverk. Däremot ska vi använda EJB:erna enligt vissa väl beprövade designmönster. Vi går igenom klasserna en itaget, skiktet business börjar med klassen MyDelegate.
  1. MyDelegate
    BusinessDelegate-klasserna agerar som proxy till sessionsbönorna. Det finns en delegate per sessionsböna och de har förmodligen exakt samma metoder som sin sessionsböna. Syftet är i första hand att innehålla all kod som har med EJB:er att göra så att den koden inte sprids ut lite överallt i presentation-skiktet. En delegate MeetingHandlerDelegate som hanterar allt som har med möten att göra skulle kunna se ut ungefär så här:
    class MeetingHandlerDelegate {
        MeetingHandlerHome meetingHandlerHome;

        MeetingHandlerDelegate() {
            meetingHandlerHome = //JNDI-uppslagning av sessionsbönan MeetingHandlers home interface.
        }

        void bookMeeting(...) throws UCalendarException {
            try {
                MeetingHandler meetingHandler = meetingHandlerHome.create();
                meetingHandler.bookmeeting();
                meetingHandler.remove();
            catch (//all EJB related exceptions) {
                throw new UCalendarException(...);
            }
        }

    }
    Detta fungerar bara om sessionsbönan ät tillståndslös (vilket är att föredra). Om den inte är tillståndslös blir det lite krångligare men det tar vi om behovet av tillståndsfulla sessionsbönor dyker upp.

    Egentligen ska en SessionFacade (se nedan) representera en aktör. Vi borde alltså ha haft en sessionsböna User i stället för MeetingHandler. Problemet är att vi har så många use case som ska utföras av aktören user att jag är rädd att den sessionsbönan skulle bli ohanterligt stor. Därför får vi dela upp den på något sätt. Min tanke här var att ha en SessionFacade som hanterar alla mötesrelaerade use case men jag är inte alls säker på att det är en lämplig uppdelning.
  2. MyFacade
    Detta är en SessionFacade, dvs en sessionsböna som har hand om flödeskontrollen för ett use case. Observera att vi aldrig anropar entitetsbönorna direkt utan bara deras SessionFacade. Det är enklast om den är tillståndslös och har en metod per use case. Den metoden innehåller då flödeskontrollen för allt som har med det use caset att göra, däremot gör den inget av jobbet. Metoden kommer att påminna mycket om scenariona i use case-modellen.

    Kommunikationen mellan en BusinessDelegate och en sessionsböna sker över nätverket via RMI. Av prestandaskäl ska vi hålla nere antalet nätverksanrop. Om BusinessDelegaten behöver hämta mycket data är det därför olämpligt att ha metoder i stil med getData1, getData2 osv. Istället gör vi en metod, getData, som returnerar ett objekt vars enda syfte är att inkapsla allt data (kallas ValueObject). Detta objekt skapas av sessionsbönan, överförs till BusinessDelegaten som läser dess innehåll, sedan används det inget mer.
  3. MyEntity
    SessionFacaderna anropar i sin tur entitetsbönor. En entitetsböna representerar en entitet, till exempel en rad i en databas. En entitetsböna ska i första hand betraktas som den persistenta entitet den representerar, inte som ett flyktigt javaobjekt. Konceptet entitetsbönor är ett utmärkt sätt att skapa en objektorienterad bild av tex en relationsdatabas och att koppla operationer (alltså metoder) till entiteterna. Det ska finnas en entitetsböna per entitet (Möte, Kontakt, Grupp, Adressbok osv). Det är helt OK att ha flera lager av entitetsbönor, tex kan en entitetsböna Adressbok ha underliggande entitetsbönor Kontakt och  Grupp). Entitetsbönorna får bara anropas av SessionFacader eller andra entitetsbönor. De ska ha lokala interface, alltså inte remote interface (av prestandaskäl).
  4. Tjänster (visas inte i diagrammen nedan)
    Det finns tjänster som till exempel att logga eller att skicka mail som varken är en SessionFacade eller en entitet. Dessa ska hanteras av sessionsbönor som bara ska anropas av SessionFacader.
  5. MyDAO
    Entitetsbönorna får inte innehålla någom SQL-kod, det vore att bryta mot regeln om hög sammanhållning. En entitetsböna är ansvarig för en entitet, inte för databasen. Började vi skriva SQL lite överallt skulle enitetsbönorna snart bli hemskt grötiga. Entitetsbönornas vy av databasen är istället en eller flera DAO:er. Det kan vara lämpligt med en DAO per entitetsböna, vi kan till exempel ha en klass MeetingDAO. Dess metoder ska inte vara mappade på något sätt mot databasens tabeller utan ska vara utformade så att de utför arbete entitetsbönan har nytta av. DAO:n MeetingDAO kan till exempel ha dessa metoder (MeetingVO är ett ValueObject, beskrivet under MySession, som innehåller allt data om ett möte) :
    MeetingVO  getMeeting(<<primary key>>)
    Collection getMeetingsWithContact(<<contact>>)
    Collection getAllMeetings()
    void createMeeting(MeetingVO)
    void updateMeeting(MeetingVO)

    DAO:erna hör egentligen till skiktet integration men eftersom deras gränssnitt är orienterat mot entitetsbönorna måste det skapas av de som skriver business-skiktet.


Integration

Skiktet integration består av DAO:er enligt vad som diskuterades i stycket om business ovan. En DAO består dels av ett interface och dels av en klass som implementerar det. Syftet är precis det som beskrevs i stycket "definition-implementation" under "designens grundprinciper". Interfacet innehåller definitionen som är helt oberoende av hur sql-satserna ser ut, den är till och med oberoende av om det över huvud taget finns några sql-satser. Implementationen (dvs klassen MyPostgresDAO i diagrammet nedan) innehåller all kod som är relaterad till en viss databashanterare (postgres i vårt fall). Det enda som händer om vi byter databashanterare är att vi får skriva en ny klass MyXXXDAO som används i stället för MyPostgresDAO. Interfacet MyDAO påverkas inte alls och inte heller någon EJB. Är det tjusigt eller är det tjusigt?

Design av ett use case

Efter denna blixtkurs i design och uCalendars arkitektur är det dags att designa koden för use case:t överför pengar mellan konton. Först tar vi itu med skiktet business.

Business

Vi börjar med att välja controller (dvs SessionFacade) eftersom det är den som tar emot en operation som utförs på detta skikt. Kandidater till Controller är klasser i domänmodellen som kan anses hantera överföringen, till exempel Kassör och Kassa. Vi väljer Kassa med tanken att alla use case som startas i kassan ska hanteras av den SessionFacaden. Kan hända kommer det i framtiden visa sig att det blir ohanterligt många use case, då får vi dela på klassen Kassa men tills vidare har vi den som SessionFacade. Scenariot vi ska implementera innehåller två systemoperationer, startaOverforing och angeKontoOchBelopp, vilket skulle innebära att klassen Kassa skulle ha dessa två metoder. När det handlar om EJB finns det emellertid mycket att vinna på att ha hela use case:t i en metod, nämligen att all flödeskontroll och transaktionshantering kan ske i den samt att vi kan använda en stateless sessionsböna (lättare och antagligen snabbare). Vi låter därför operationen startaOverforing stanna i användargränssnittet, den resulterar bara i att en ny skärmbild visas. Allt arbetet i modellen utförs vid operationen angeKontoOchBelopp. Detta är dock inget bra namn på en metod i en Controller, den ska i stället heta något som antyder vilket use case det handlar om. Vi kallar den helt enkelt overforing.

Det är bäst att använda ett interaction diagram för att göra design. Förmodligen är ett collaboration diagram överskådligast men jag är inte vän med mitt UML-verktyg så det måste bli ett sequence diagram, se figuren nedan till vänster:

Det var sessionsbönan, nu tittar vi på entitetsbönorna. En titt i domänmodelen säger oss att två konton ska uppdateras (de som överföringen görs från och till). Dessa konton är entiteter, dvs de finns redan i databasen. De är inte något som sessionsbönan kassa skapar, den slår istället upp dem i databasen. Denna uppslagning hanteras av ejb-metoden find:
































Nu kan vi misstänka att för att kunna hantera uttag och insättning måste kontoeniteterna kolla i kontospecifikationen i kontokatalogen om det till exempel ska dras någon uttagsavgift. Detta är kanske inte sant i en riktig bank eftersom eventuella uttagsavgifter antagligen beräknas när det är dags att beräkna räntan. Om uttagsavgifter ska dras från kontot redan när uttaget sker kan det se ut så här:

Kontokatalog kan i sin tur ha entiteter Kontospecifikation som innehåller specifikationer för en viss typ av konto. Det tar jag inte med här för att diagramet inte ska bli för stort. Notera bara att det vore vödvändigt med samlingar av Kontospecifikation för varje kontotyp eftersom det till exempel inte räcker att veta räntenivån just nu, vi måste kunna ta reda på nivån vid alla tillfällen under ett år för att kunna beräkna räntan vid årsskiftet.

Nu funderar vi i stället på kvittot. Enligt SSD ska ett kvitto returneras och enligt domänmodellen ska det inehålla objekt av typen Overforing med objekt av typen OverforingsRad. Ska dessa vara entiteter som sparas i databasen? Det beror på om vi vill kunna få fram information om dem vid ett senare tillfälle. Vi antar nu att så inte är fallet. Det ger oss tre val: Metoden overforing returnerar kvittoinformationen, SessionFacaden (Kassa) görs till en tillståndsfull sessionsbönasamt att kvittoinformationen skapas i skiktet presentation. Det finns ingen anledning att gå igenom allt besvär med en tillståndsfull sessionsböna bara för att kunna anropa en annan metod som ska returnera kvittoinformationen, vilket lämnar oss två alternativ kvar. Det finns för och nackdelar med båda två men vi väljer att låta metoden overföring returnera informationen:

Slutligen konstaterar vi dels att det som i diagrammen ovan är ritat som skiktet presentation i själva verket är en BusinessDelegate som finns i presentation. Denna innehåller exakt samma metod som SessionFacaden, overforing(franKto. tillKto, belopp). Vidare konstaterar vi att alla entiteter ska ha varsin DAO. I diagrammet ritar jag (av utrymmesskäl) bara ut DAO:n till en av kontoentiteterna:

I diagrammet ovan har DAO:n bara en metod, det är dock inte sant. Den måste också innehålla de metoder som krävs för att hantera EJB-containerns anrop av ejbLoad, ejbStore osv. Som minimum lär detta innebära metoderna updateKto(ktoVO), createKto(ktoVO) samt deleteKto(prim key).

När vi nu är klara med sekvensdiagrammet för skiktet business kan det vara bra att rita ett klassdiagram för detsamma:

Några kommentarer om diagrammet ovan.
Ifall överföringen skulle loggas skulle vi införa en tjänst "loggning" representerad av en tillståndslös sessionsböna "logger" som hanterade all loggning. Den skulle anropas av Kassa.

Nu är designen av businss-skiktet klar. Nu kan tre olika aktiviteter fortsätta parallellt: Koda business, designa och koda presentation (kodning sker mha en BD som returnerar fejkade värden i stället för att anropa business om business ännu inte är färdigkodat), designa och implementera resource.

Resource

Här handlar det om att bestämma hur entiteterna ska lagras i databasen. Domänmodellen och designmodellen för business-skiktet talar om vad som ska sparas men absolut inte hur. Business innehåller en objektorienterad modell av entiteterna men det säger inget om hur en relationsdatabas med samma eniteter ska vara uppbyggd.

Jag är inte bra på att designa relationsdatabaser så tyvärr kan jag inte ge några råd om det.

Integration

När både business och resource är designade går det bra att designa och koda integration. Designen är ganska trivial:

Presentation

Slutligen är det dags att designa presentation. Tack vare ramverket vi skrev räcker det med följande:

Undantag

Checked exceptions ska användas för alla fel som inte kan upptäckas vid programmering. Det är till exempel att typiskt checked exception att det inte finns tillräckligt mycket pengar på ett konto för att kunna utföra ett uttag. Å andra sidan är det ett typiskt unchecked exception att försöka läsa på för högt index i en array. I det första fallet ska vi kasta en egenutvecklas klass som ärver av Exception, i det andra fallet kastas automatiskt ArrayIndexOutOfBoundsException som  ärver av RuntimeException.

Här kommer några enkla riktlinjer för hur undantag ska användas:
  1. Undantagen ska ha namn som beskriver felet.
  2. Skapa inte onödigt många undantagsklasser. Finns det många undantagsklasser finns det risk att metodsignaturer ändras hela tiden när vi kommer på nya undantag. Det kan räcka med en klass per "subsystem", tex kastar alla klasser i paketet java.sql undantaget SQLException oavsett vilket fel som uppstod i databasen. Om vi använder få klasser är det klokt att låta undantaget innehålla tex en sträng som beskriver felet.
  3. Undantag ska inte propagera upp genom flera lager/skikt. Tex kan databashanteringen i DAO:erna resultera i att ett SQLException kastas. Detta måste fångas i DAO:n eftersom EJB:erna inte ska innehålla någon databasrelaterad kod. När det fångats kan tex ett EJBException kastas i stället för att meddela EJB:n att den anropade metoden i DAO:n misslyckades. På samma sätt måste alla EJBException och RemoteException fångas senast i BusinessDelegaten och omvandlas till något undantag som kan hanteras i presentation.
  4. Många fel kan åtgärdas direkt men en del fel måste ramla hela vägen upp till klienten och visas för användaren. Ett annat subset av felen ska loggas. För dessa båda ändamål är det lämpligt att ha (minst) en speciell Action och (minst) en Dispatcher för felhantering. För att returnera dessa kan vi ge ActionFactory en ny metod: getErrorAction(ScreenName, Exception)

Paket

Hela diskussionerna som fördes om "low coupling - high cohesion" och om inkapsling i avsnittet om designens grundprinciper gäller paket precis lika väl som klasser. Det är viktigt att ett pakets publika gränssnitt är stabilt, om ändringar sker inuti ett paket är inte lika farligt. Desto fler beroenden det finns av ett paket, desto stabilare måste det vara.

Vad gäller uCalendars börjar alla paketnamn med att identifiera organisationen där de utvecklas:

se.kth.isk


Därefter kommer produktens namn:

se.kth.isk.ucalendar

Sedan kommer namnet på skiktet där paketet finns:

se.kth.isk.ucalendar.presentation
se.kth.isk.ucalendar.business
se.kth.isk.ucalendar.integration

Därefter gäller följande för de tre olika skikten:

Presentation

Ramverket placerar vi i ett eget paket:

se.kth.isk.ucalendar.presentation.framework

Action- och Dispatcher-klasser placeras i olika paket beroende på funktionalitet, tex

se.kth.isk.ucalendar.presentation.meeting
se.kth.isk.ucalendar.presentation.note

Om det blir några generella klasser med "bra att ha"-metoder placerar vi dem i ett eget paket:

se.kth.isk.ucalendar.presentation.util

Business och Integration

Här har vi inget ramverk så det blir bara paket i stil med

se.kth.isk.ucalendar.business.meeting
se.kth.isk.ucalendar.integration.meeting

och

se.kth.isk.ucalendar.business.util
se.kth.isk.ucalendar.integration.util

Tester

JUnit-tester läggs i ett paket test inuti det paket de testar. Tester för klasser i tex paketet
se.kth.isk.ucalendar.business.meeting hamnar alltså i ett paket se.kth.isk.ucalendar.business.meeting.test

Säkerhet

Authorization

Oavsett om vi kommer att använda olika rättigheter eller inte ska uCalendar byggas med stöd för detta. I första vevan innebär detta inget mer än att alla metoder i API:et (dvs i SessionFacaderna och därmed i BusinessDelegaterna) måste ha en inparameter som representerar användarens identitet. 

uPortal har ett inbyggt API för rättigheter som vi ska använda men jag är ännu inte helt klar över hur det fungerar. Detta löser vi dock genom att hitta på en egen typ för den extra inparametern som nämndes ovan. Vi låter den vara ett interface se.kth.isk.ucalendar.business.security.Authorization. Tills vidare kan det vara utan metoder och vi kan låta den extra parametern ha värdet null. Syftet är att den ska kunna innehålla antingen uPortals klass som hanterar rättigheter eller en egenutvecklad rättighetsklass om uPortals rättighetshantering inte ska användas. När vi kan mer om uPortals rättighetssystem kan vi bestämma vilka metoder interfacet ska ha.

Authentication

All inloggning sker i uPortal och hur den fungerar behöver vi inte bry oss om.

Hur stoppar vi en attack som sker genom att någon skriver en egen EJB-klient som försöker koppla upp sig direkt mot vårat EJB-skikt? Såvitt jag kan se kan vi inte styra över det i och med att uPortal inte använder J2EE:s deklarativa säkerhetshantering. Vi får helt enkelt ange att vem som helst får köra alla metoder (method-permission i deployment descriptorn (DD) sätts till unchecked) och sedan får vi påpeka risken för den systemansvarige.

Loggning

Vi har inga intrångsförsök att logga eftersom vi inte hanterar autentitiering. Vi ska logga fel enligt vad som angetts i UCM och SS.

Transaktioner

Mycket finns att säga om transaktionshantering både vad gäller EJB:er och databaser. Hur transaktioner sköts har stor inverkan på prestanda och kan också leda till väldigt svårfixade buggar. Det här är bara en extremt kort skiss av en metod som borde funka för oss. Tänk bara aldrig "det där löser sig i databasen"...

EJB:er

Vi ska använda container managed transactions (CMT), dvs deklarativa. Alla metoder i SessionFacaderna ska exekveras i egna transaktioner. En ny transaktion ska påbörjas när metoden startar och slutar när metoden returnerar. Detta åstadkommer vi genom att i DD sätta transaktionsattributet till RequieresNew för alla sessionsbönor. Alla metoder i entitetsbönor ska köras i den transaktion som startades i den anropande metoden i SessionFacaden. Det innebär att alla metoder i entitetsbönor ska ha transaktionsattributet Required i DD.

Några ord om JBoss: JBoss gör det enkelt för oss genom att bara ha en instans av varje entitetsböna laddad åt gången. Detta innebär att vi inte behöver bekymra oss om transaktionsisolering (se nedan) eller lås i JBoss. Om vi vi standardjboss.xml ändrar comitt-option från B till A kommer JBoss att cacha entiteternas värden och synka med databasen väsentligt mer sällan. Detta kan ge klart bättre prestanda men funkar bara om all access till databasen sker via JBoss.

Databasen

Transaktionsisolering

Postgres har default transaktionsisoleringsnivån Read Commited vilken innebär att en transaktion inte ser uppdateringar gjorda i en annan transaktion som inte har commitat. Däremot ser en transaktion alla uppdteringr gjorda av andra commitade transaktioner. Det är alltså möjligt att två olika select i samma transaktion returnerar såväl olika rader som olika innehåll i raderna. Normalt är detta inte något problem men det är bra att vara medveten om det. Read Commited är snabb och erbjuder oftast tillräckligt skydd.

Uppdateringar gjorda i samma transaktion syns även innan transaktionen har commitats.

Om en transaktion ska göra en uppdatering och hittar en rad som redan är uppdaterad av en annan ocommitad transaktion väntar transaktionen tills den andra transaktionen har avslutats och kollar sedan igen om raden ska uppdateras.

Lås

Postgres låser normalt inte, varje transaktion får i stället sin egen bild av databasen. Bilden visar databasen som den såg ut när transaktionen påbörjades. Detta innebär att vi aldrig kan veta om läst rad verkligen finns i databasen, den kan ha blivit borttagen av en annan parallell transaktion. Detta är noramlt inte heller något problem. Det går att låsa en rad mot parallella uppdateringar genom att istället för SELECT skriva SELECT FOR UPDATE.

Några fler designmönster

Dessa var inte med i designexemplen ovan men kan säkert vara användbara i uCalendar.

EJB

Ett mönster för att generera primära nycklar (kallas Sequence Blocks)

Det är ofta bäst att ha en primär nyckel som inte betyder något, dvs att inte ta någon av tabellens "datakolumner" som primär nyckel. Det här är ett mönster för att generera unika heltal, vilka är lämpliga att använda som primära nycklar.

Antag att en godtycklig entitetsböna, ClientEntityBean, behöver primära nycklar. Vi kan också anta att det finns flera enitetsbönor som behöver primära nycklar för olika entiteter. Detta kan lösas med en tjänst PrimaryKeyGenerator, som implementeras så här mha en sessionsböna (som använder en entitetsböna):

När en ny entitet av ClientEntityBean skapas kommer dess ejbCreate() att behöva en primär nyckel. Det får den genom att anropa PrimaryKeyGenerator på detta vis: int myPK = primaryKeyGenerator.nextPrimaryKey(entityName); PrimaryKeyGenerator returnerar då  en int som är ett större än den som sist  returnerades för samma entitet.

I databasen finns en tabell som innehåller namnet på alla entiteter och värdetpå senast returnerade primära nyckel förvarje entitet. För att inte behöva anropa databasen varje gång en ny primär nyckel ska genereras har PrimaryKeyGenerator själv en räknare för varje primär nyckel (i blocks) som ökas med ett för varje nyckel som genereras.

När blockSize nycklar har genererats är det dags att synka med databsen. Då anropas PrimaryKey.nextKeyAfterIncrementBy() som ökar raden i databasen där senaste värdetför denna nyckel finns med blockSize och sedan returnerar det nya värdet.

Detta mönster exekveras snabbast om PrimaryKeyGenerator har ett lokalt interface (den kommer ändå aldrig att anropas av något annat än entitetsbönor) samt om vi, till skillnad från vad som angavs under avsnittet om transkationer, ger nextPrimaryKey() transaktionsattributet Required och nextKeyAfterIncrementBy() attributet RequiresNew. Det första för att genereringen av en primär nyckel hänger ihop med att skapa den nya entiteten. Det andra för att den nya entiteten kanske skapas i en lång transaktion, då är det onödigt att låsa tabellen med primära nycklar under hela den transaktionen. Det skulle fördröja andra transaktioner som behöver skapa primära nycklar.

ValueListIterator

Detta mönster används när en klient till skiktet business, tex uCalendar-kanalen, behöver browsa bland en massa rader i databasen. Antag till exempel att användaren har en jättestor adressbok som hon/han bläddrar i. Det vore mycket långsamt att skapa en instans av en entitetsböna Contact för varje rad i adressboken som ska visas. Vi kan i stället lösa det genom att ha en sessionsböna som läser direkt ur databasen enligt diagrammet nedan. BusinessDelegaten (UserDelegate) får då returnera en Iterator som kan hantera browsandet.

ValueListIterator läser alla rader med kontakter från databasen mha en DAO och kan sedan cacha alla eller vissa av dessa rader lokalt. ValueListIterator har metoder typiska för en iterator för att browsa bland kontakterna. Klassen SomeIterator är ingen BusinessDelegate men har samma funktion, nämligen att dölja anropen av EJB:er för resten av presentation. Den har samma metoder som sin sessionsböna. Användaren kan nu scrolla i sin adressbok utan att det behöver göras ideliga databasanrop. När användaren bestämmer sig för att göra en ändring i adressboken används inte längre ValueListIterator, då är det i stället dags att gå på entitetsbönan Contact.

Antingen använder vi samma ValueListIterator oavsett vad som ska browsas, då måste den ta emot en parameter som talar om vad vi vill browsa, eller skriver vi en unik klass för allt som ska kunna browsas, i så fall vora det lämpligare att kalla den till exempel ContactIterator.

Om sessionsbönan ska hålla reda på cursorn (alltså vilken som är aktuell rad) måste den vara tillståndsfull. Ett alternativ är att sessionsbönan är tillståndslös och att SomeIterator håller reda på cursorn och skickar med den i varje metodanrop av sessionsbönan (så ser det ut i diagrammet).