Thursday, February 11, 2010

Hur jag använder DDD i mitt projekt

Jag har haft möjlighet att praktisera domändriven design i ett av mina uppdrag, och jag tänker försöka beskriva hur det är att jobba på det sättet, varför det kan leda till högre kvalitet och produktivitet samt dela med mig av några tips och erfarenheter.

Med "domän" i domändriven design menar vi verksamhetsområdet, det företaget ägnar sig åt. I ett av mina uppdrag har jag verkat inom domänen elektroniska passersystem där jag arbetat med en hyfsat stor produkt med några hundra tusen rader kod och några tiotal manår utveckling bakom sig, den interagerar med olika typer av användare och andra system med hjälp av en varierad flora av gränssnitt och är sedan länge i produktion med fler än tusen installationer runt om i världen. En liten del av den här modulariserade produkten handlar om att hantera bokning av saker som konferensrum, tvättmaskiner och tennisbanor.

Inom den här modulen kan vi snäva in domänbeskrivningen till bokning, rätt och slätt. Det råkar vara en utomordentligt bra domän för att introducera DDD i en organisation, och dessutom för att skriva en artikel om arbetet, eftersom det innehåller ett visst mått av komplexitet men samtidigt är någorlunda bekant för de flesta. Problemet vi var ute efter att lösa med vår modell är att hålla reda på vem som får boka vad och när, och dessutom att leverera information till andra delar av systemet som fysiskt styr passage genom dörrar, utifrån gjorda bokningar.

Den här modulen stod i tur att genomgå större förändringar av flera orsaker, men man kunde konstatera att den var väldigt svår att jobba med, utan något entydigt centrum för affärslogik som man kunde studera och testa. Det var utspritt över hela det vertikala ledet, från JSP-sidor till SQL-satser. Genomgående var att typningen var väldigt primitiv, så man kunde se metodsignaturer som innehöll både tre, fyra och fem long som ibland var primärnycklar och ibland millisekunder.

PeriodConfiguration getPeriodConfiguration(long personId,
                                           long resourceGroupId,
                                           long bookersAccessCategoryId,
                                           long from,
                                           long to)


De klasser som fanns hade sällan något beteende eller några som helst begränsningar i vad de kunde innehålla, utan var normalt tomma databehållare med ganska yxigt (som det ofta blir) översatta engelska namn. Kort sagt, det fanns helt enkelt ingen egentlig modell, och det är tyvärr ingen ovanlig situation.

Det här ville vi förändra. Vi ville bygga en modell av hur bokningssystemet skulle fungera, som var förändringstålig och robust, och gick att testa och studera helt utan infrastruktur. En modell som vi kunde visa upp och resonera kring tillsammans med en domänexpert, kanske i skissform men allra helst hela vägen ner i koden.

Domänexperten och det gemensamma språket

Tillgång till en domänexpert är en av den viktigaste förutsättningen för att kunna bedriva domändriven design. Man kan och bör tillgodogöra sig så mycket baskunskap i ämnet som möjligt på egen hand, eftersom domänexperten ofta är en upptagen person, men för att verkligen få maximal utväxling i koden behöver man bolla med någon som verkligen vet hur det fungerar. Vårt team hade tur, vi har nämligen relativt god tillgång till en domänexpert som samtidigt är vår produktägare. Bokningsdomänen är som sagt rätt snäll, men som i varje domän och varje produkt finns det ett antal egenheter. I vårt fall handlar det om att känna till hur äldre och konkurrerande system används och vad de klarar av, vad som är efterfrågat bland användarna och hur interaktionen med existerande och framtida hårdvara ska fungera, bland annat.

Vår domänexpert tycker att det är roligt och givande att delta i modelleringsdiskussioner, och jag tror att det inte är alltför sällsynt om man utnyttjar tiden effektivt. Man bör komma väl förberedd utan att ha surrat fast sig vid bestämd idé på förhand. Ställ konstruktiva frågor som kan blottlägga avgränsningar och viktiga regler. "Förekommer det nånsin att...", "Kan man betrakta X och Y som olika varianter av Z?", "Händer det att A och B samtidigt är tomma?" och så vidare. Ge domänexperten utrymme, och undvik att hemfalla åt datatekniska termer utan var uppmärksam på hur han eller hon uttrycker sig. 


En annan viktig ingrediens i domändriven design är det gemensamma språket. En modell som är både är djupt förankrad i hur domänen fungerar och som uttrycks i ett språk som används både av domänexperten, av programmerarna sinsemellan och som dessutom återfinns i koden blir väldigt förändringstålig. Ofta ligger det gemensamma språket nära fackspråket, men det kan även innehålla nya ord som behövs för att utrycka den typ av struktur man behöver för att skriva mjukvara, eller stryka några ord som betyder ungefär samma sak för att fokusera på ett enda med kristallklar betydelse. Vi baserade vårt språk i stor utsträckning på vad saker och ting hette i de användargränssnitt som redan fanns, eftersom det med tiden hade satt sig hos alla som kommit i kontakt med systemet. Min erfarenhet är att man ska akta sig för att försöka "rätta till" ologiska termer som är väl etablerade, eftersom det blir mycket svårare att upprätthålla koplingen mellan hur man pratar och hur koden är namngiven.

Men att hitta ett gemensamt språk är inte en helt och hållet passiv övning. Ibland identifierar man ett begrepp som kanske inte finns explicit i fackspråket eller i något gränssnitt, men som är väldigt användbart är man bygger en strukturerad modell som ett datorprogram. Ett bra exempel från vårt projekt är bokningsförfrågan. Både när man vill veta vad som är möjligt att boka och när man faktiskt utför en bokning så inkluderar det vem som bokar, vad man vill boka och när man vill boka det. Det här flöt liksom omkring i våra diskussioner och lät ungefär "Ok, vi säger att en lägenhet vill boka tvättstuga 3 den 22:a oktober mellan 14.00 och 18.00...", så vi introducerade det som ett explicit begrepp vilket betyder två saker: det är någonting som både programmerare och domänexpert är överens om vad det betyder, och det finns en klass i domänmodellen med samma namn:

public class Bokningsförfrågan implements ValueObject<Bokningsförfrågan> {

    Bokningsobjekt bokningsobjekt;
    Bokare bokare;
    Pass pass;
       ...
}


Vi karaktäriserade det som ett värdeobjekt, value object, och speglade den språkliga definitionen i koden genom att konstruktorer och jämförelseoperationer som equals() och hashcode() garanterar att man aldrig stöter på en bokningsförfrågan som saknar exempelvis bokare, och där två bokningsförfrågningar med från samma bokare som avser samma bokningsobjekt vid samma tidpunkt i alla avseenden kan betraktas som lika. På så vis har vi höjt abstraktionsnivån från strängar och heltal till en klass med tydlig karaktär och beteende, och som speglar någonting som vi kan prata med domänexperten om. När önskemål om förändringar kommer i framtiden kommer man att kunna svara på dem mycket snabbare, och vår kod blir mer robust.               

Domänmodellen är som synes programmerad på svenska i så stor utsträckning som möjligt, ett rätt så kontroversiellt beslut i teamet och tror jag bland programmerare i allmänhet. Jag var själv motståndare till det för inte så länge sedan, men min erfarenhet är att det absolut är värt att göra det om alla inblandade talar svenska. Översättningar blir sällan perfekta, och framför allt förlorar man den intima kopplingen mellan det talade och skrivna ordet man har om koden är på svenska. En övning till läsaren: heter det a booking eller a reservation på engelska?

En kraftfull modell

Nu ska vi titta på ett litet men matnyttigt exempel på hur man kan gå från ett uttalande av domänexperten till konkret kod om man har en kraftfull objektmodell, lättarbetade stödbibliotek och ett gemensamt språk. Så här skulle det kunna låta:


Man ska kunna ställa in ett bokningsobjekt så att man högst får ha till exempel tre aktiva bokningar under samma månad.


Det här är en av många bokningsregler som styr huruvida en bokning kan ske eller inte. Var och en av dessa regler implementerar gränssnittet Bokningsregel som kan en enda sak: att svara ja eller nej på om den tillåter en bokningsförfrågan (den skarpsynte anar kanske att det handlar om Specification-mönstret). Vi behöver alltså titta på det aktuella bokningsobjektets inställningar, och om det finns en gräns för hur många bokningar en bokare får ha per månad, kontrollera att bokaren har högst så många bokningar på det här bokningsobjektet under den månad som passet infaller. Låter rätt enkelt, och definitivt någonting som man kan resonera kring tillsammans med domänexperten. Så här tydlig kan koden bli som faktiskt utför den här kontrollen:
          

public class MånatligBegränsning implements Bokningsregel {

    @Override
    public boolean tillåter(Bokningsförfrågan bokningsförfrågan) {
        Bokningsobjekt bokningsobjekt =
bokningsförfrågan.bokningsobjekt();
        Regelinställningar regelinställningar = bokningsobjekt.inställningar();
        Integer gräns = regelinställningar.perMånadPerBokare();

        if (ejSatt(gräns)) {
            return true;
        } else {
            TimeInterval passetsMånad = månadFörPasstart(bokningsförfrågan.pass());
            Bokare bokare =
bokningsförfrågan.bokare();
                    List<Bokning> bokningarUnderMånad = bokare.listaAktivaBokningar(bokningsobjekt, passetsMånad);

            return bokningarUnderMånad.size() < gräns;
        }
    }
   
    private boolean ejSatt(Integer gräns) {
        return gräns == null;
    }


    private TimeInterval månadFörPasstart(Pass pass) {
        TimePoint passStart = pass.somIntervall().start();
        CalendarDate datumFörPasstart = passStart.calendarDate(TIDSZON);

        return datumFörPasstart.month().asTimeInterval(TIDSZON);
    }

}

Huvuddelen av jobbet görs av Bokare-klassen, i den metod som listar alla bokningar för ett visst bokningsobjekt under ett intervall, exempelvis en kalendermånad. Den här koden ligger så nära domänexpertens förståelse av hur systemet fungerar att det nästan går att parprogrammera tillsammans. Vi har gått igenom och exekverat scenariotester på sprintavslut, vilket fungerade riktigt bra och var en välkommen omväxlig. Kanske inte något man gör varje gång, men det kan definitivt föra delar av organisationer närmare varandra och öka utomståendes förståelse och intresse för mjukvaruutveckling.

Fokusera

En sak som man brukar betrakta som del av det man kallar strategisk design är att att identifiera vad som är kärnan i verksamheten, core domain, och fokusera på det. Det låter självklart, men det är tyvärr väldigt vanligt att man lägger ner massor av arbete på saker som tillför liten eller ingen affärsnytta till produkten, antingen omedvetet eller för att man tycker att man hittat ett intressant problem.

För oss är kärnverksamheten (under det här projektet och i den här delen av produkten) det vi formulerade tidigare: att hålla reda på vem som får boka vad och när, och dessutom att leverera information till andra delar av systemet som fysiskt styr passage genom dörrar, utifrån gjorda bokningar. Företaget säljer väl integrerade helhetslösningar med både mjuk- och hårdvara, och allt måste mynna ut i att dörren till tennishallen öppnas det klockslag som bokningen är gjord. 

Fundamentet i vår affärslogik är förstås starkt knutet till tidshantering - datum, månader, tidpunkter, intervall och så vidare. Det är dock inte något som är unikt för vår produkt, eller ens för bokningsdomänen. Det hör till programmeringens allmängods och ett exempel på vad man på engelska kallar generic subdomain. Här vill vi lägga så lite energi som möjligt och istället använda färdiga och kraftfulla bibliotek. Att hacka på tid- och datumhantering i Java är lite av spel mot öppet mål, men jag påminner ändå om hur det ofta ser ut när man försöker bygga något med standardbiblioteket:

   
            Calendar cal = getCalendar(start.getTimeInMillis());
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            long startTimeMidnight = cal.getTimeInMillis();

            ...

            if (start.getTimeInMillis() == startTimeMidnight || bp.getFromTime() >= start.getTimeInMillis()) {
               periods.add(bp);
            }

            ...
 
            /* next day */
            start.set(Calendar.DAY_OF_YEAR, start.get(Calendar.DAY_OF_YEAR) + 1);
            startingPoint = Long.valueOf(start.getTimeInMillis());
      

Vi valde att jobba det väldigt trevliga Time and Money, ett fritt bibliotek som huvudsakligen är skrivet av Eric Evans, i väntan på att ett nytt och bättre standardbibliotek ska dyka upp. 

I den bokningsregel som begränsar antal bokningar per månad behöver vi veta vilken månad som passet infaller. Ett pass börjar i en tidpunkt (TimePoint), som är någonting med minimal längd och maximal precision, exempelvis 2009-09-09 09:09:09.000 GMT. Den tidpunkten infaller på något datum i varje tidszon, som är ett väldigt annorlunda begrepp än tidpunkt och representeras av en explicit typ, CalendarDate.

I vårt exempel och i tidszonen GMT+1 innebär det 2009-09-09. Slutligen infaller ett datum såklart under någon månad, här september 2009, en instans av klassen Month:       


         TimePoint passStart = pass.somIntervall().start();
        CalendarDate datumFörPassStart = passStart.calendarDate(TIDSZON);
        Month månad = datumFörPassStart.month();

 
Vår klass Bokare kan lista bokningar inom vilket tidsintervall som helst - en dag, en månad eller från idag och två veckor framåt, och på tre tydliga rader kod kan vi klara av att formulera vår fråga till Bokare-klassen. Genom att utnyttja Time and Money frigör vi massor av utvecklingstid som vi istället kan lägga på det som verkligen är unikt i vår produkt, och koden kan hållas snygg och prydlig.

38 comments:

餐廳 said...

如果擬任為輸贏是最重要的事,那你輸了........................................

ya said...

能付出愛心就是福;能消除煩惱就是慧。........................................

玉鳳 said...

Never put both feet in your mouth at the same time, because then you will not have a leg to stand on.............................................

v奎峰奎峰 said...

Unable to give you a heart. so have a reply to push up your post. ........................................

陳石薇 said...

hello~welcome my world~<. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1208茹宣dinoreale said...

如果你不思考未來,你便不會有未來..................................................

ElvisS_Scholten0188 said...

激情成人聊天室情色成人辣妹胸部辣妹視訊露奶辣妹自拍三點全露內衣秀台灣成人貼圖成人電影院三點全裸免費視訊辣妹av圖裸體自拍色情聊天美女視訊g點色情訊息淫女火辣av辣妹圖片免費視訊聊天室情色天空調情上床圖片裸體自拍走光照片走光視訊情色成人18成人區火辣美女成人vcd成人影片下載本土av性愛情慾淫妹美女聊天性愛聊天室女生自慰影片免費看a圖淫婦巨乳辣妹視訊成人女生自慰方法免費情色限制級a片穿幫情色下載情色網站

80808waldod_brogden said...

happy to read~ thank you!........................................

香廷 said...

責人之心責己,恕己之心恕人。........................................

韋于倫成 said...

Make hay while the sun shines.......................................................

ErinBurling曹 said...

go2av免費影片sex 0401 影音 live 秀視訊聊天交友 色美眉台中援交aa 片試看 a免費視訊聊天網 情人34C波霸美女 香蕉鮑魚俱樂部,免費av 免費線上 aa 片試看 g點無碼a片 性愛姿勢 辣妹sex story 視訊聊天室 sex888免費看影片波霸美女寫真 視訊美女pixnet 成人交友hibb 4u成人視訊 情人080視訊網 私密論壇sex888免費看影片論壇 Show-live視訊聊天室 辣妹貼圖a片天堂 a片免費看 情色視訊禁地論壇 日本 a 片,無碼影片,美女,sexy,a片天堂 小魔女免費影城 視訊美女sogo 色論壇 s38live秀 卡通aa片免費看自拍天堂 洪爺免費線上歐美A片段觀看 亞洲東洋影片 0941 影音視訊聊天室 18成人85cc影城0204movie 成人漫畫區月光論壇 a片-情色成人影片 免費視訊toolbox 707網愛聊天室 1111辣妹貼圖,寫真集辣妹 av無碼月光論壇辣妹視訊 視訊辣妹主播脫衣秀 aio性愛dvd辣妹影片直播 黑澀會美眉 mv 高雄視訊,qq 美女貼圖片區 5i176 免費視訊秀 168論壇視訊辣妹 a片天堂s383視訊 bt電影下載,aa 片 sexy girl video movie 080cc成人色網 後官0951主入口 視訊聊天評比 視訊交友聊天室 no8 sogo論壇視訊辣妹

皇雅婷豪 said...

你不能決定生命的長度,但你可以控制它的寬度..................................................

kittycha said...

成人情色論壇 aa 片俱樂部視訊i68美女 視訊女郎成人 情色 aa片免費看影片色漫畫帝國 免費a網,免費視訊辣妹 免費av999 sex影片視訊分享區 免費視訊聊天 ex jp成人-免費聊天室 夜色網 avhigh 視訊交友av1688 閃亮天使520聊天室 bt名模論壇 性感辣妹,sex女優 免費影片直播網 免費無碼影片 g8mm 網 34c高雄視訊聊天 正妹影音mmshow 成人a片網 oec 喔伊細辣妹視訊交友 yam交友天堂 777視訊美女 666成人網 哈啦視訊聊天室 性愛姿勢,sogo 色論壇 新竹援交a片免費線上看 a片-癡電車漢 kk俱樂部thmt aa成人漫畫 18禁聊天 18禁成人網 免費影片觀賞 洪爺免費a影片線上直 jp激麻a電影 聊天室交友b shop 成人視訊mela ,g點,免費a片,免費18影片 聊天室環球辣妹聊天室 90691 免費 aa 片試看情色文學 線上 aa 片試看嘟嘟,免費線上a電影 成人交友qk176 辣妹聊天室 90691 AV 前線 avdvd ut聊天室找一夜女 girl5320 貼片et免費影片下載 美國免費 aa 片試看aio 倉井空免費影片 wc123美色女影城 jp成人網

玫友 said...

nice job! waiting for your new artical. .................................................................

洪筱婷 said...

嘿,你的部落格不錯耶~~只是想跟您問聲好!! .................................................................

王婷珊 said...

一個人的價值,應該看他貢獻了什麼,而不是他取得了什麼.................................................................

志穎志穎 said...

一定要保持最佳狀況呦,加油!!!期待你發表的新文章!.................................................................

婷珊 said...

人不能像動物一樣活著,而應該追求知識和美德............................................................

江彭珮陽v彥璋 said...

人生是故事的創造與遺忘。............................................................

佳皓佳皓 said...

Practice makes perfect.............................................................

雅俊芬凱陳許 said...

來給你加油打氣,祝福大家開開心心。 ............................................................

王伯亞王伯亞 said...

No garden without its weeds.............................................................

婉婷 said...

No one knows the weight of anothers burden. ..................................................

姿柯瑩柯dgdd憶曾g智曾 said...

enjoy your artical, thank you............................................................

妍妮妍妮 said...

真正仁慈的人,會忘記他們做過的善行,他們全心投入現在的工作,過去的事已被遺忘。..................................................

黃威宇 said...

「仁慈」二個字,就能讓冬天三個月都溫暖。..................................................

佳瑩佳瑩 said...

死亡是悲哀的,但活得不快樂更悲哀。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

許惠吟許惠吟 said...

不會從失敗中找尋教訓的人,成功之路是遙遠的。.................................................

李吳俊其彥柏 said...

愛,拆開來是心和受兩個字。用心去接受對方的一切,用心去愛對方的所有。......................................................................

韋雅萍志 said...

Quality is better than quantity...................................................................

怡于名君 said...

No pains, no gains..................................................................

雅明修任 said...

讚啦~~多謝分享!!>ˍ<............................................................

瑛萍 said...

愛情不是慈善事業,不能隨便施捨。......................................................................

凱v胡倫 said...

這一生中有多少人擦肩而過?而朋友是多麼可貴啊!......................................................................

陳智雅威宜 said...

很精彩的部落格 期待你的繼續加油............................................................

文滢 said...

要保持更新呦,加油!!!期待你的新文章!!!............................................................

于庭吳 said...

文章雖然普通,但意義卻很大~~^^~~ ..................................................

黃英吳思潔吳思潔邦 said...

精彩的部落格 值得一推再推 支持你......................................................