Dette indlæg er et afsnit i serien om Continuous Integration.

Nu er der jo ikke meget sjov ved bare at check koden ud, så lad os få bygget noget. Vi starter med at tilføje en task:

<tasks>
  <nant>
    <baseDirectory>d:\dev\framework</baseDirectory>
    <executable>d:\dev\tools\nant\nant.exe</executable>
    <buildFile>framework.build</buildFile>
    <targetList>
      <target>clean</target>
      <target>compile-tests</target>
    </targetList>
  </nant>
</tasks> 

Task elementer kan indeholde mange forskellige opgaver hvor vi her har valgt at køre et NAnt script. Man kunne vælge at bruge MSBuild til at bygge VS solutions med, men jeg flytter nogle ektra ting ned i build scriptet som også kan køres på udvikler maskiner. Mit NAnt script kalder så MSBuild scripts, så du har egentlig tre niveauer for hvor du skal bygge:
CC, NAnt, MSBuild (Tilføj selv flere efter behov). Hvilket niveau du vælger afhænger af dine ønsker, men jeg fortrækker at man har mulighed for at kontrollere både build og unit-tests inden man checker kode ind i source kontrol systemet.

Dette script bygger to target: Et clean target (så vi slipper af med gammelt bras) og et compile target som bygger unit test, men som afhænger af at frameworket et bygget. Tilføj selv flere targets efter behov.

Hvis du har installere på en frisk maskine uden Visual Studio, så løber du nok ind i problemer med NAnt. Problemer udmønter sig i brok over at installDir for net-2.0 ikke er angivet og det er selvom du tvinger den til at bruge net-3.5. For at løse det finder du din NAnt.exe.config fil og søger efter readregistry elementer. Attributen sdkInstallRoot skal rettes. I mit tilfælde (jeg bruger Windows SDK 6.1) til: SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1\WinSDKNetFxTools\InstallationFolder. Det skal lige gøres et par steder, så kører det. Dette kan dog være løst i en nye version af NAnt når du læser dette. Jeg har taget udgangspunkt i TreeSurgeon som giver dig både et buildscript og de tilhørende filer.

Når vores projekter er bygget kan vi kopiere bygget til vores filserver med denne lille build publisher:

<buildpublisher>
  <sourceDir>d:\dev\framework\build</sourceDir>
  <publishDir>\\fileserver\framework</publishDir>
</buildpublisher>

Så er der altid adgang til det nyeste build.

Når nu det hele kører så mangler vi bare den automatiske del. Til det formål har CC en række triggers hvoraf ForceBuildTrigger er default. Det mest oplagte for CI er at indsætte en IntervalTrigger i triggers blokken:

<triggers>
  <intervalTrigger name="Continuous" seconds="60" />
</triggers> 

Med denne trigger vil CC checke subversion hvert minut. Hvis der er sket ændringer vil den sætte gang i et nyt build. Et natligt rebuild kan tilføjes med en ScheduleTrigger

<scheduleTrigger time="23:30" buildCondition="ForceBuild" name="Scheduled" /> 

Bemærk at buildCondition er sat til ForceBuild mod default at være IfModificationExists. Dette kan også bruges for intervalTrigger hvis man skulle få brug for det. Der er mange muligheder for at tilpasse triggers, så f.eks. der ikke bygges på bestemte tidspunkter med FilterTrigger eller at ScheduleTrigger kun bygger på hverdage. Se mere under http://confluence.public.thoughtworks.org/display/CCNET/Trigger+Blocks

Nu er det bare at checke noget nyt kode ind og vente på at de dit byg (hvis du altså ikke vil vente til kl 23.30).

Jeg har tidligere arbejdet i et firma hvor vi brugte mange soft-delete (eller som det hedder i salgsbrochuren - Fuldt revisionsspor), dvs. man sletter ikke i databasen, men markerer istedet rækken for slettet. I vores tilfælde blev slettetidspunktet markeret, så objektets tilstand kunne genskabes på forskellige valørdatoer. De første man lægger mærke til er naturligvis at databasen vokser hurtigt og at alle queries indeholder 'DeletionTime NOT NULL'. Det var praksis og påkrævet af revisorer.

Man kan mene mange ting om soft-delete og Frans Bouma mener at Soft-deletes are bad. Inden vi ser lidt nærmere på hans løsningsforslag, så lad mig give ham ret i at, hvis du ikke har revisorer på nakken, så bør du nok finde på en anden løsning end soft-deletes. Gammelt data er trods alt.. ja, gammelt. Jeg ved godt at vi lever i Google-tid og man bare kan søge igennem alt, men lader du Google indeksere dine business data?

Vi har allerede etableret et behov for at bevare data af juridiske grunde, men det er også værd at nævne at data forbliver slettet. Gammelt data bruges ikke til at "spole" objektets tilstand tilbage ved at "undelete" data, altså fjerne slettetidspunktet i databasen. Derimod oprettes en ny linie som en kopi af den gamle. Ellers ville vi miste det fulde revisionsspor. Vi er ikke i det Frans Bouma kalder tilfælde to (men beskriver først). Det er også noget rod.

En naturlig løsning er at arkivere gammelt data, som man arkiverer gamle emails (eller hvordan er det nu lige er man får håndteret dem?). Frans Bouma foreslår at bruge database triggers. En delete trigger kan kopiere det slettede data over i anden tabel. Et forslag som jeg ikke kan stå 100% bag. Jeg kan se ideen i at kopiere slettet data over
i en anden tabel. Det er alligevel oftest, at man kun skal bruge de aktuelle data og i de tilfælde hvor man skal se gammelt data vil det kunne klares med et view.

Det er database triggeren som giver mig problemer. Der er mange gode og dårlige grunde til at bruge triggers eller lade være, men min helt store anke i dette tilfælde er manglende triggers. Hvis man får slettet triggeren fra database så får man ingen fejlbeskeder. Uden at vide det kan man få slettet en masse data som burde være kopieret. Man opdager det først når man skal bruge de gamle data. En stored procedure ville kunne gøre det samme og man vil helt sikkert opdage hvis den manglede.

Jeg vil mene at soft-deletes har deres plads, specielt for enterprise data. Men derfor er det stadig tilladt at organisere sine data bedst muligt. Bare lov mig at du ikke bruger en delete trigger som er kritisk for dine data.