You are on page 1of 210

FrontPiece

.NETEnterpriseDesignwithVisualBasic.NETandSQL
Server2000
JimmyNilsson
Publisher:SamsPublishing
FirstEditionDecember12,2001
ISBN:0-672-32233-1,384pages
TheonlybookthatusesVisualBasic.NET,SQLServer2000,and.NET
toprovidestrategiesforsolvingthekeyproblemsdevelopers
encounterwhendesigningcomponentservicesforenterprise
applications.
N Mostup-to-datecoverageavailableaboutVB.NET,.NET
componentservices,andSQLServer2000.
N Usesasoftwareengineeringapproachtodiscusshowtouse
valuabletechniquestosolvekeyproblemsfacedwhendealing
withcomplexdistributedsystems.
N Detaileddiscussionoftheprosandconsforeachpotential
solutionbylookingatissuessuchasperformance,scalability,
andmaintainability.
.NET Enterprise Deisgn with Visual Basic.NET and SQL Server 2000
discussesfactorsandopinionsdevelopersshouldconsiderinorderto
createhigherqualitydesignsandapplications.Theauthorusesone
large-scalebusinessapplicationthroughoutthebookasthebasisfor
allexamplestoclearlyillustrateconceptsbeingdiscussed.Coverage
alsoincludesavarietyofaspectsaboutdesignintheworldof.NET;
explanationsofthebusinessanddataaccesslayersofapplication
design;solutionsforproblemssuchascodestructure,soliderror
trapping,andhowtobuildindebuggingsupport;discussionofhowto
designlargerprojectswithmorerobustsystemsandreusable
components,andcomparisonofcomponentsolutionstostored
proceduresolutions.
.NETEnterpriseDesignwithVisualBasic.NETandSQLServer2000
Copyright2002bySamsPublishing
Allrightsreserved.Nopartofthisbookshallbereproduced,storedin
aretrievalsystem,ortransmittedbyanymeans,electronic,
mechanical,photocopying,recording,orotherwise,withoutwritten
permissionfromthepublisher.Nopatentliabilityisassumedwith
respecttotheuseoftheinformationcontainedherein.Althoughevery
precautionhasbeentakeninthepreparationofthisbook,the
publisherandauthorassumenoresponsibilityforerrorsoromissions.
Norisanyliabilityassumedfordamagesresultingfromtheuseofthe
informationcontainedherein.
LibraryofCongressCatalogCardNumber:10-93572
PrintedintheUnitedStatesofAmerica
FirstPrinting:December2001
040302014321
Trademarks
Alltermsmentionedinthisbookthatareknowntobetrademarksor
servicemarkshavebeenappropriatelycapitalized.SamsPublishing
cannotattesttotheaccuracyofthisinformation.Useofaterminthis
bookshouldnotberegardedasaffectingthe validityofanytrademark
orservicemark.
VisualStudio.NET,Windows,Windows2000,WindowsXP,Windows
.NETServer,VisualBasic.NET,C#,SQLServer2000,VisualBasic6,
VisualStudio6,andVisualC++6,andMicrosoftOperationsManager
2000areall registeredtrademarksofMicrosoftCorporation.
DB2andTivoliareregisteredtrademarksoftheIBMCorporation.
OracleisaregisteredtrademarkoftheOracleCorporation.
SybaseSQLServerisaregisteredtrademarkofSybase.
IngresisaregisteredtrademarkofComputerAssociates.
UnicenterTNGisaregisteredtrademarkofComputerAssociates.
Warning and Disclaimer
Everyefforthasbeenmadetomakethisbookascompleteandas
accurateaspossible,butnowarrantyorfitnessisimplied.The
informationprovidedisonan"asisbasis.Theauthorandthe
publishershallhaveneitherliabilitynorresponsibilitytoanypersonor
entitywithrespecttoanylossordamagesarisingfromtheinformation
containedinthisbook.
Associate Publisher
Linda Engelman
Dedication
I dedicate this book to my wonderful wife Lotta, our great son Tim,
and our second child whom we are expecting early next year. I love
you guys and without you, nothing would mean anything!
Foreword
IneveraskedJimmywhyexactlyheaskedmetowritethisforeword,butIaminclinedtothink
thatitwasfor"historicalreasons:JimmypublishedhisfirstarticleonMTSandCOM+
developmentabouttwoyearsagoonVB-2-The-Max,thesiteIrunwithotherItalianauthorsand
developers.
ItwasmoreorlessJimmy'sfirsteditorialexperience,soIwaspreparedtodosomeeditingbefore
publishingthearticle.Iamnottalkingaboutfixinggrammarorstyle-afterall,thecombinationof
anItalianguywhoeditsanEnglishdocumentbyaSwedishauthorisn'tgoingtodeliverexciting
results-butIanticipatedtheneedtoreorganizethematerial,askforfurtherclarificationsonkey
points,andsoon.Inshort,thekindofthingsthatmakeatechnicalarticleagood,readablearticle.
Tomysurprise,I didn'tfindanythingwortheditinginthedraftmanuscript,and,infact,thearticle
wentonlineinitsoriginalform.Iwasratherimpressed,becauseitdoesn'thappenfrequently-in
general,andvirtuallyneverwithfirst-timeauthors.(YoucanreaditandtheotherarticlesJimmy
wroteforusathttp://www.vb2themax.com/ArticleBank.asp.)
IliketothinkthatthegoodfeedbackJimmygotfromourreadersencouragedhimtoproposehis
materialtoothersites,magazines,andconferencesfordevelopers,suchastheEuropeanandU.S.
editionsofVBITS,whereJimmynowspeaksregularly.ThisexplainswhyIwassogladwhenJimmy
toldmethathewaswritingabook.Inmyopinion,hehad towriteabookbecauseitwasthe
naturalupshotofhisabilityasawriterandateacher.
ButJimmyisn'tjustateacherandanauthor;heisanexperienceddeveloperwhohastakenpartin
manyprogrammingprojectsofallsizes,includingcomplexenterprise-levelapplications-andit
showsinthisbook.Forexample,readChapter5 forathoroughanalysisofthemanyapproaches
youcanhavewhiledesigninglarge-scale,multitierapplications.Theauthorobviouslyknowsthe
theoryandcancombineiteffectivelywiththeconstraintsofeverydaycodinglife.Ibelievehehas
agreattalentforfindingtheappropriatemidpointbetweenarigorousandsomewhatscientific
approachandmorepractical,hands-on,tips-and-tricks-liketechniquesthatallprogrammersmust
adopttogetthejobdone.
NET Enterprise Design with Visual Basic .NET and SQL Server 2000 isdifferentfrommostofthe
.NETbooksyoufindonbookshelvestoday.Whilemostoftheofferingsinthiscategorydescribethe
newlanguagesyntaxorexcitingtechnologies,suchasWebServices,Jimmypreferredtofocuson
theoptionsyouhavewhenyoudesignalarge-scaleapplication,andthecommonmistakesyou
shouldavoidinthisdelicatephase.Ofcourse,Ibelievethatbooksthatteachlanguagesandthe
.NETinternalsareimportant-afterall,Iamtheauthorofonesuchtextbook-butitisevidentthat
applicationdesigncanhaveafar-reachingimpactonperformance,scalability,andextensibility.
Yoursmartestsolutionsandthemostefficient,hand-optimizedcodecaneasilybeoffsetbya
nonoptimalarchitecture.Evenifyou'recode-addicted,thisbookwon'tdisappointyou,because
you'llfindalotofT-SQLandVisualBasic.NETcode.
IespeciallylikethewayJimmyexplainsthetradeoffsofeachapproachheintroduces,teachingyou
toweighalltherelevantfactors,andultimatelyhelpingyouselectthemostappropriatesolution.If
you'regoingtowritecomplex,enterprise-level.NETapplications,thisbookisforyou.
-FrancescoBalena
AuthorofProgramming Microsoft Visual Basic 6 andProgramming Microsoft Visual Basic .NET
(MicrosoftPress).
Founderandeditor-in-chiefofwww.vb2themax.com.
ContributingeditorandEditorialAdvisoryBoardmemberofVisual Studio Magazine,Fawcette
TechnicalPublications.
Preface
Tomyknowledge,nobookhasyetbeenpublishedthatfocusesonCOM+ComponentServices,
storedprocedures,and interactions.Thisbookdoesjustthat,butwithinthenewworldof.NET
ComponentServices,VisualBasic.NET,andSQLServer2000.
Thebookdiscusseskeyproblemsthatmostdeveloperswillfaceastheydevelopenterprise
applicationsforthetargetedplatform.Iwilloffersomesuggestionsforsolutionstotheseproblems
anddiscusstheprosandconsofeachsolutionvis-a-visperformance,scalability,maintainability,
dataintegrity,andsoon.
Ofcourse,it'sriskytowriteabookandofferproposalsforhowtosolvedifferentproblemsatthis
earlystageoflifeofthe.NETplatform.IfIhadwaitedayear,itwouldhavebeenmucheasier,
becausemanybestpracticeswouldhavehadtimetoevolveandmature.Sayingthat,nowisthe
timetoreadthisbookifyouwanttostayontheforefrontofnewtechnology.Justkeepinmind
thatmyrecommendationsaremeanttoprovideyouwithinspirationratherthandefinitive
solutions.Theusefulnessandapplicabilityofasolutiondependsonthecircumstances.
Unlikeotherbooksonthesamesubject,thisbooktakesasoftwareengineeringapproachandis
focusedondesign.Thisdesignfocusisrelevantbecauseit'smoreimportantwhenprogramming
forVisualBasic.NET,forexample,tobemoredesignfocusedthanforVB6.
Insteadofdescribingdifferenttechniquesfoundintheplatformwork,Imainlydiscusshowthey
canbeusedinsolvingkeyproblemsfoundinreal-worldapplications.Thus,thisbookisproblem-
orientedinsteadoftechnique-oriented.Youareaskedto thinkabouttheproposalsIpresent,
considerhowyouwillhandletheproblemsonyourown,andassesstheprosandconsofany
solution.
Target Audience of This Book
Thetargetaudienceofthisbookisreaderswhowanttolearnmoreaboutbuildingenterprise
applicationsinthe.NETplatform.It'sassimpleasthat.Thebookiswrittenforarchitects,
developers,teamleaders,andtechnicalmanagers.
Ihavetriedtomakethebookcomponent-oriented.Thatis,insteadofrepeatingwhateveryother
booksays aboutsomething,Ihavekeptbackgroundinformationtoaminimumandexpectyouto
havesomebackgroundknowledgebeforereadingthisbook,includingexperiencewiththe
following:
N VisualBasic.NETorC#
N The.NETFramework
N ComponentServicesfromMTS,COM+,or.NET
N T-SQLinSQLServer6,7,or2000
N ADO.NET
ItwillbehelpfulforyoutohavesomeexperienceofVB6,COM,andADOaswell.Ifyoudonot
havebackgroundknowledgeorexperiencewiththesetopics,Ipointyouinthedirectionofother
bookstoreadtogetuptospeedatappropriatetimesinthechapters.
Focus of This Book
Let'slookatapicturethatrepresentssomethingthatIassumeyouareusedto,namely,alayered
architecture.AnexampleisfoundinFigureP.1.
Figure P.1. A layered architecture: the focus of the book.
YoucanseeinFigureP.1thatIdemonstratethearchitecturewithWindowsFormsfortheclient
application.Itcould justaswellhavebeenanASP.NETapplication,forinstance.However,
WindowsFormsandASP.NETarenotthefocusofthebook.Theshadedareasinthefigurearethe
focus,including
N Thecomponentsintheapplicationserver
N Thestoredproceduresinthedatabaseserver
N Ahelperlayerattheconsumerside
Why I Am Writing About the Chosen Platform
Insteadofgivingyouadirectanswertothis,I'dliketoexplainitinthreeparts.I'lldiscusswhyI
choseVisualBasic.NETandSQLServer2000,butIwillstartwithmyreasoningbehindusing.NET
ComponentServices.
Why .NET Component Services
Foracoupleofyearsnow,MTSandCOM+ComponentServiceshavebeenverystrongplatforms
forbuildingenterpriseapplications.With.NET,it'sbecomingeasiertowritecomponentsforCOM+
ComponentServices(or.NETComponentServices,asitisnowcalled)andyoucanexpressmany
designdecisionsinyourcodewiththehelpofattributes.Justasbefore,youwillbenefitgreatly
fromtheinfrastructurehelpyougetfromtheservices.AndforVBdevelopers,it'snowpossibleto
benefitfromservicessuchasObjectPoolingforthefirsttime.
SomeoftheComponentServices-orEnterpriseServices,asMicrosoftoftencallsit- have
equivalentsinthe.NETFramework.Thisisn'tsostrangebecausemuchofthe.NETFrameworkwas
builtbythesamedeveloperswhobuiltCOM+.Youdon'thavetomakeachoicebetweenoneorthe
other,asmostoftentheycomplementeachother.Whenusingautomatictransactionsinthe.NET
Framework,COM+transactionswillbeused.Thesamegoesforactivity-basedsynchronization,
looselycoupledevents,andsoon.
AsIwrite,thereisn'tmuchbuzzbeinggeneratedaboutComponentServices.Ithinkthisis
becausetherearesomanynewandexcitingtechnologiescomingwith.NET,suchasASP.NETand
XMLWebServices,thatarereceivingmostoftheattention.Mybeliefisthatthiswillchangevery
soon.BehindtheentrypointofXMLWebServicesandASP.NET,COM+componentsand
ComponentServiceswillstillbedoingthelion'sshareofthework.Itisasiftheoperatingsystem
hasgrownandisprovidingmoreservices.Forthemostpart,it'sbeneficialtousethoseservices
insteadofwritingsimilaronesofyourown.IexpecttoseetheprogrammingmodelofComponent
Servicesbeingusedforalongtimeinthefuture.
Why Visual Basic .NET
BeforeIstartedworkingonthisbook,IthoughtlongandhardaboutmovingfromVB6toC#
insteadoftoVisualBasic.NET.IfinallydecidedthatIcouldn'twriteabookforoldC++
programmers.Whenyou'remostlyworkinginanenvironmentsuchasVB,youget"assimilatedto
itandtalkandthinkinaspecificway.(Theoppositeisalsotrue.ItwouldbehardforaC++
persontowriteagoodbookaboutVisualBasic.NET.)Therefore,IwillfocusonVisualBasic.NET.
Still,programmerswhofocusonC#canstillbenefitfromreadingthebookaslongastheyare
preparedforitsVBstyle.(ThisisespeciallytrueforC#programmerscomingfromVB,ofcourse.)
Theyjusthavetotranslatethecodesamplestotheirownlanguage,whichoughttobesimple
enough.
Nomatterwhat.NETlanguageyouchoose,weareall.NETdevelopersandweareallwritingfor
thecommonlanguageruntime.
Why SQL Server 2000
Around1994,Ithoughtrelationaldatabaseswouldsoonbejustlegacyproducts.Iwaswrong.
Eventoday,relationaldatabasesarekindofstateoftheartforbusinessapplications,andSQL
Server2000isagoodexampleofsuchaproduct.
AlthoughIcouldhavechosentowriteaboutagenericdatabaseplatform,Iwouldhavelostoneof
mymainpoints,namelythatit'simportanttousethespecificcapabilitiesofthedatabaseifyou
wantthehighestscalability.IhavechosentowriteaboutSQLServer2000becauseSQLServer
hasbeenmyfavoritedatabaseplatformformanyyears.It'scompetent,comparativelycheap,and
widespread.However,mostoftheideasIdiscusscanbeconverted,forexample,toOracleand
DB2.
Organization of This Book
Thisbookisdividedintoninechaptersandincludesaseparateappendixwithsuggestionsfor
furtherreading.
InChapter1,"Introduction,IsetthescenefortherestofthebookbybrieflydiscussingCOM,
COM+,VB6,andpreviousversionsofSQLServer.Ialsodescribethe.NETFrameworkingeneral,
.NETComponentServices,VisualBasic.NET,C#,andSQLServer2000.InChapter2,"Factorsto
ConsiderinChoosingaSolutiontoaProblem,IdiscussthedifferentcriteriaIwillusethroughout
thebooktoevaluatemyproposalstokeyproblems.
InChapter3,"Testing,Istarttodescribekeyproblems andsolutions-adiscussionthatwill
continuethroughouttherestofthebook.Thischapterlooksattheextremelyimportant-yetoften
forgotten-aspectoftestapplicationsthoroughly.Inthischapter,Idiscussandshowasolutionof
howyoucanautomate agooddealoftesting,thussavingyourselftimeinthefuture.Ialsodiscuss
strategiesforusingassertions.Chapter4,"AddingDebuggingSupport,discussesimportant
preparationsyoushouldconsiderwhenbuildingenterpriseapplications,suchaspreparingfor
debugging.Welookcloselyataddingtracingsupport,errorlogging,andhowtohandle
configurationdata.
WindowsDNAhasbeenasuccessfularchitectureforseveralyearsnow.InChapter5,
"Architecture,Idescribeanewarchitectureproposal(bothsimilaranddissimilartoWindowsDNA)
thatyoucanusetobettertakeadvantageofthenewplatform.Ialsodiscusssuitablecode
structuresforthedifferentlayersinthearchitecture.TheninChapter6,"Transactions,welookat
goodtransactionaldesignandwhyitiscrucialforbuildingasuccessfulenterpriseapplication.In
thischapter,Ifocusonhowtowritethemostefficienttransactions,whilekeepingcorrectnessin
mind.IalsodiscussmethodsformakingthecodeeasilytochangefrompureT-SQLtransactionsto
COM+transactions,iftheneedarises.
MovingontoChapter7,"BusinessRules,Idiscussdifferentbusinessrules,wheretheyshouldbe
located,andhowtheyshouldbeimplemented.InChapter8,"DataAccess,Ifocusonanewly
developeddataaccesspatternanditshelper.Thispatternreducesroundtripsbetweenthe
BusinesstierandtheDatatier,andshortenstransactionlengthsandincreasesdebuggability.
Finally,Chapter9,"ErrorHandlingandConcurrencyControl,discussesstrategiesforerror
handlingbothinstoredproceduresandin.NETcomponents.Italsodiscussesstrategiesof
concurrencycontrolforsituationswhendataisdisconnectedfromthedatabase,bothoptimistic
andpessimisticprotocols.
Topics That Aren't Covered
IdecidedearlyonthatIwantedtowriteaconciseandfocusedbook.Therefore,Ihadtoskipmany
interestingandimportanttopics.Becausethefocusofthebookisdesignandprogrammi ngforthe
applicationserverandthedatabaseserver,Iwon'tdiscussWebForms,WindowsForms,andWeb
Servicesingreatdepth.Inaddition,in-depthdiscussionofthefollowingtopicsisbeyondthescope
ofthisbook:
N .NETRemoting
N MSMQ,QueuedComponents(QC),LooselyCoupledEvents(LCE),CompensatingResource
Manager(CRM)
N Multithreading
N DelegatesandEvents
N ADO.NET
Eachofthesetopicsisinterestingandimportant,buttheycanfillentirebooksontheirown.Irefer
youtoAppendixA,"SuggestionsforFurtherReading,foralistofsuggestedbooksshouldyou
wanttoreadaboutthesetopics.
The Book's Web Site
You'llfindtheWebsiteofthebookatwww.jnsk.se andwww.samspublishing.com and.Thissite
includesthefollowing:
N Resultsofseveralperformancetests,executedafterversion1of.NEThasbeenreleased
N Acontinuouslyupdatedlistofsuggestionsforfurtherreading
N Thecodefromthesamplesandthetoolsdiscussedinthebook
N Theerrataforthebook
A Few Final Comments
Whenyoureadthebook,youwillfindthatthesourcecodehasalmostnocommentsatall.Thisis
nothowIthinkyoushouldprogram,ofcourse.Ihavedoneitthiswayforreasonsofbrevityalone.
Ifyoudownloadthecompletesourcecodefromthebook'sWebsiteatwww.jnsk.se willfindthat
thecodeisheavilycommented.
Ihavetriedtoensurethatthecontentinthisbookisascorrectaspossible.However,thereis
alwaysariskforerrors,especiallybecauseIwrotethebookearlyon.Ifyoufindanerror,please
e-mailmeandI'llpostacorrectiononthebook'sWebsite.
Insum,I'vetriedtowriteabookthatIwouldlovetoreadmyself.Hopefully,youwillenjoyittoo.
JimmyNilsson
www.jnsk.se
Jimmy.Nilsson@jnsk.se
Ronneby,Sweden
September2001
About the Author
Jimmy Nilsson istheowneroftheSwedishconsultingcompanyJNSKAB.Hehasbeenworking
withsystemdevelopmentfor13years(withVBsinceversion1.0)and,inrecentyears,hehas
specializedincomponent-baseddevelopment,mostlyintheMicrosoftenvironment.Hehasalso
beendevelopingandpresentingcoursesindatabasedesign,object-orienteddesign,andsoonata
Swedishuniversityforsixyears.JimmyisafrequentspeakeratFawcetteandWroxConferences
andhaswrittennumeroustechnicalarticles.Evenso,heconsidershimselftobemainlya
developer.
Acknowledgments
TherearesomanytowhomIowethanks.Iamindebtedtothemforprovidingfeedback,forbeing
soundingboards,andforbeingsourcesofinspiration.Itisimpossibletothankthemall,butI
wouldliketomentionfourpeopleinparticularwhohavemeantmoretomethananybodyelse
duringthisproject.
ToFrancescoBalena:Forbeingmymentorthroughoutmywritingcareerandforwritingsucha
niceforeword.
ToDanBystrm:Forbeingagreatsoundingboardandsourceofinspirationthroughoutthewhole
ofmyprofessionallifeasadeveloper.
ToIngemarLundberg:Forprovidingmorefeedbackandparticipatinginmorediscussionsthan
anybodyelsewhileIwasworkingonthisbook.
ToShannonPahl,ProgramManagerforCOM+,Microsoft:Foransweringmymanyquestions,but
mostlyforreadingthroughthebookandprovidingfeedback.
ThereareagreatmanymorepeoplewhomIwouldliketothank.Thefollowinglistisn'tatall
complete,butthosewhohave meantthemosttomewhenwritingthecontentare(inalphabetical
order):
MikeAmundsen,RickardBengtsson,JoeCelko,MartinForsberg,TommyGrndefors,Kenneth
Henningsson,PeterL.Henry,TiborKaraszi,MarttiLaiho,JoeLong,MichaelD.Long,TomMoreau,
Per-OlaNilsson,JohnRobbins,EnricoSabbadin,EgidioSburlino,CharlieSvahnberg,andJohan
Svanstrm.
Ialsooweabigthankstotheteamthathelpedmetransformmyideasintosomething
understandable,theSamsteamwithwhomI'vebeenworkingveryclosely.
SondraScott,AcquisitionsEditor,talkedmeintowritingthebookinthefirstplaceandhasbeena
tremendoussupportthroughouttheentireproject.LydiaWest,LanguageEditor,transformedmy
"SwenglishintoEnglish.ShannonLeuma,DevelopmentEditor,madewondersofeachandevery
chapter,bothintermsofstructureandtheclarity.HeatherMcNeill,ProjectEditor,wasinchargeof
theproductionsideofthebook.DeonSchafferandSundarRajan,TechnicalEditors,checkedthe
contentfortechnicalerrorsandproposednumerousimprovements.Andfinally,PatKinyon,Copy
Editor,helpedmecleanupthemistakestherestofusmissed.
Chapter 1. Introduction
IN THIS CHAPTER
N TheHistoryofCOM,MTS/COM+,VB,andSQLServer
N TheNewWorldof.NET
N COM+1.5ComponentServices
N VisualBasic.NKET
N C#
N SQLServer2000
Inthischapter,IwillbrieflydiscussthehistoryoftheComponentObjectModel(COM)uptothe
stateof.NETtoday.Inthisdiscussion,IwilltellsomehorrorstoriesfromthedarkagesofCOM,
VisualBasic6(VB6),andCOM+,andwilllookattheadvancesmadeintheindustryinrecent
years.Iwillalsotouchonkeytechnologies,suchas.NETComponentServices,VisualBasic.NET,
C#,andSQLServer2000,thatIintendtouseformyexamplesanddiscussionsthroughoutthe
book.Tounderstandthischapterandtherestofthebook,Iassumethatyouaresomewhat
familiarwithVisualBasic.NET,commonlanguageruntime,andsoon,andsoIwon'tdiscussthese
topicsindetailinthischapter.Withthatcaveatoutoftheway,let'sgetstarted.
The History of COM, MTS/COM+, VB, and SQL Server
Althoughweallknowthatprogrammingdidn'tstartwithCOM,amajorshiftintheindustry
occurredwhenCOMbecamepopularinthemid-1990s,soitseemsagoodplacetostartourlook
atthetechnology'shistory.Fromthere,we'llmoveontoMTS/COM+ComponentServices,before
touchingonVBandSQLServer.
COM
PerhapsoneofthemostimportantideasbehindCOMwastomakeitatechniqueformovingaway
frommonolithicapplicationstowardcomponent-basedapplications.Assuch,COMisabinary
model,notasource-codemodel.Thismightmakefrequentreusealittleeasiertoaccomplish,
becauseyoureusebinarycomponentsinsteadofsource-codeclasseswithCOM.Anotherimportant
benefitistheuser'sabilitytotakeadvantageofblack-boxreuse,whichmeansthatthereuseronly
pullssomeropestomakethecomponentbehaveaswantedwithoutknowinghowthecodeis
writtenforthecomponent.Black-boxreusewasnotpossibletoachievewhensourcecodewas
reused.Ifyou,asareuser,areabletoviewthecodeyouarereusing,itmightbemoreacceptable
forthevendortorequireknowledgeoftheimplementationofthereusedcodeinsteadoftotally
hidingallimplementationdetails,whichmostoftenmakeslifeeasierforthereuser.Thatmeans
thatthereusermusthaveafirmgraspoftheinnerworkingsofthecomponentitwantstoreuse
beforeheorshecanmakeitworkcorrectly.
AnotherideabehindCOMwasthatitneededtobelanguage-independent;inessence,withCOM,
programmersareabletochooseamongseveraldifferentlanguages.Tomakethiswork,certain
rulesneedtobecompliedwith,andtheinterfacesofthecomponentsmustbedescribedwith
InterfaceDefinitionLanguage(IDL),whichisthencompiledandtransformedintotypelibraries.
ThedesignersofCOMwereoftheopinionthatobject-orientationwasthewaytogo,andthat
object-orientationprovidedasoundbasefromwhichtowork.OneproblemCOMaddressedwas
versioning,sothatacomponentcould beextendedwhilestillbeingcompatiblewithexisting
consumers.Meanwhile,COM(orratherDCOMforDistributedCOM)alsofocusedoncreatinga
methodtosplitapplicationssothatdifferentpartsoftheworkloadcanbeexecutedondifferent
machines.
MTS/COM+ Component Services
COMwasabigleapintherightdirection,butMicrosoftTransactionServer(MTS)tookCOMtothe
nextlevel.MTSaddedsomeruntimeservicesforCOMcomponents,suchasdeclarativedistributed
transactions,declarativerole-based security,administrativesupport,andresourcepooling,to
nameafew.Inaway,MTSaddedservicestotheoperatingsystemthatdeveloperswhowrote
distributedapplicationspreviouslyhadtobuildontheirown.SoonafterMTSwasreleased,pure
DCOMapplicationsbecameararityintheindustry.
Afriendofminewhoworksasaprojectmanageroftenasksmehowlongitwilltakemetowritea
certainprogram.Ialwaystellhimthatitwilltaketwodays.(Thatis theuniversalanswer,right?)
Hisresponseisalwaysoneofshock:"Howcanitpossiblytaketwodaystocheckacheckbox?he
asks.Infact,withMTS'sattribute-basedprogramming,myfriend'sshockisabitmorejustified,
becausemoreprogrammingcanbeaccomplishedthroughsimpledeclarationorbysettingan
attributevalue.TransactionsinCOM+isoneexample,referentialintegrityinSQLServeranother.
WithWindows2000,MTSwasupgradedtoCOM+.WhileMTSwasactuallyanadd-ontoWindows
NT,COM+wasnativelysupportedbyWindows2000.Thissimplifiedtheprogrammingmodelquite
abit.ItwasoftensaidthatMTS+COM=COM+.Inessence,COM+joinedthesetwopreviously
separatedparts.Inaddition,COM+addedcomponentservices,suchasObjectPooling(OP),
CompensatingResourceManager(CRM),andQueuedComponents(QC).
VB
IstartedmyadventuresinWindowsprogrammingwithVB1.Istillremembermyfirstmajor
mistakeinthatenvironment.It'sactuallyratherembarrassingtoshare,butitmakesme
sentimentalinaniceway.Iwassupposedtobuildamenuapplicationfromwhichseveralother
applicationsshouldbestarted.Itwasanearly"portalofsorts.Mybosswantedmetousepictures
insteadoftextonthebuttons,soIusedpictureboxesinsteadofcommandbuttons.Unfortunately,
Imanagedtoinsertpictures,eachhavingasizeofapproximately200KB,butonly1KBwas
actuallyofinterestandshownfromeachofthem.Theexewasover2MBlarge,whichmadefor
veryslowstartuponthehardwareatthattime,especiallyinrelationshiptowhatarocket-science
applicationitwas.
Severalyearsandmanymistakeslater,VB4wasreleased.ItwasthefirstreallyCOM-competent
versionofVisualBasic.Still,itwasn'tuntilVB5andVB6thattheCOMprogramminginVBbecame
popular.Oneofthe great(butsometimesalsonegative)aspectsofVBisthatithidesseveral
detailsofCOM,suchasclassfactories,referencecounting,user-definedinterfaces(ifdesired),IDL,
andQueryInterface.Youcan'tachieveasgoodaperformanceoutofVBasyoucanfromC++,
buttheproductivityisverygood.Still,insomecamps,VBisconsideredmoreofatoythanatool
foraprofessionalprogrammer.Infact,I'vebeenaskedmorethanonceifIgetpaidtoprogramin
VB.ThegoodnewsisthatVisualBasic.NETwillchangetheopinionsofsomeofthosewhoseeVB
asjustatoy.Soyounolongerhavetobeembarrassedtoadmititifitisyourfavorite
programminglanguage.
SQL Server
IlearnedSQLattheuniversityinthelate1980s,anditisstillusefultoday.Actually,it'smore
importanttomasterthanever,especiallyifyoufindscalabilityvitalforyourapplications.SinceI
startedworkingwithSQLServer(version4),itsprogrammingmodel,withitstriggersandstored
procedures,hasremainedquiteconsistent.Forexample,theshiftfromversion6.5toversion7
wasamajoroneattheenginelevel,butT-SQLdidn'tchangethatmuchatall.
Afriendofmineoncesaid,"Databasedesignisimportant.Databasesareusedin102%ofall
applicationstoday.Ithinkmanydevelopersagreethatdatabasesareusedalot.Ialsothinkthat
thereisalargedifferenceinopinionregardingwhetherthedatabasetier-suchasSQLServer
2000,forexample-shouldbeusedfordata-focusedlogic.
Ifallontheextremeyesside.Youwillseemepresentmypositionthroughoutthebook;stored
procedures,forexample,willhaveacentralroleinseveraloftheproposedsolutions.However,
eventhoughIamfondofusingdatabasecapabilities,Idefinitelywanttoencapsulatetheaccess
callstothemsotheyarenotvisibletomorelayersthannecessary.
Now,let'sturnourattentionto.NET,whichcanbeseenasthefourthversionofCOM(afterpure
COM,MTS,andCOM+).Sayingthat,COMisdead,butlongliveCOM(nowcalled.NET)!
A Few Important Definitions
Beforewegettoofaralonginthebook,I'dliketodefineafewtermsIwill
beusinginthesechaptersthathavemanydifferentmeaningsinthe
industry:
N Component- IfyouagreewithSzyperski
5
,theword"component
referstoaCOMserver,suchasaDLLora.NETassembly,thatisa
binarycollectionofclasses.TheCOM+definitionofthewordrefers
toonesinglebinaryclass.IpreferthisCOM+definition.
N Tier and layer- Iusedtothinkthetermstierandlayerwere
synonyms.Overthelastfewyears,Ihavecometounderstandthat
layerislogical,whiletierisphysical.Unfortunately,thisexplanation
isn'tverydistinct.Forexample,Iliketothinkoftheserver-side
componentsasonetierandSQLServerasanother,butsometimes
theyexecutephysicallyatthesamemachine.Therefore,inthis
book,Iwillusethewordtiertorefertoanexecutionenvironment
andlayertorefertoalogicalsliceofatier.Typicalexecution
environmentsincludeASP.NET,SQLServer,COM+,Webbrowser,
XMLWebservices,andOrdinaryWin32exes.
The New World of .NET
Nowthatwehavetakenalookatthehistoryofthetechnology,we'rereadytolookatthemany
changesthathavecomeaboutwiththeintroductionof.NET.
.NET: A Whole New Platform
IrememberwhenIfirstreadMaryKirtland'sarticles,Object-OrientedSoftwareDevelopment
MadeSimplewithCOM+RuntimeServices
1
andTheCOM+ProgrammingModelMakesitEasyto
WriteComponentsinAnyLanguage,
2
inMicrosoft Systems Journal in1997.Inherarticles,Mary
discussedmanyofthethingsweknowoftodayas.NET,butshecalleditCOM+.Atthattime,it
feltlikeahugestepand,asitturnedout,itwasprobablytoobigaleapforMicrosofttotake;with
COM+1.0,theydidn'tleveragemorethanahandfulofthethingsMarydiscussed.Ialsoremember
thinkingthatseveraloftheconceptswerefamiliartomeasaCOMprogrammerinVB,andthatthe
C++guysnowwouldhavesomeoftheseproductivity-enhancingfeaturestoo.Thiswasalsothe
firsttimeIheardtheexpression"Youcanthinkofmetadataasatypelibraryonsteroids.How
manyarticlesandbookscanyoulistthathaveusedthatexpressionsince?
.NET Features
ThefollowingaresomeoftheconceptsthatMarydiscussedinherarticlesbutthatweren'tfulfilled
inCOM+1.0:
N Acommonruntimethatcouldbeusedfromanyprogramminglanguage.
N Fullinteroperabilitybetweenlanguages.
N Attributeswithacentralrole.
N Richmetadata.
N Noneedformanualcoderegardinggarbagecollection,classfactories,andsoon.
N Methodsuseddirectlyonclassesandnotonlyviainterfaces.
N Implementationinheritance.
N Nodeterministicfinalization,whichmeansthatyoudon'tknowwhenanobjectisfinalized
becausethegarbagecollectionmayhappenimmediatelyor,mostoften,muchlater.(Yes,
deterministicfinalizationwasalreadymentionedatthistime.Iwonderwhyitdidn'tupset
developersasmuchthen.)
N Astrongsecuritymodelforthecomponents.
N FunctionalitysimilartoSqlDataAdapter inADO.NET.(AnSqlDataAdapter canbeusedto
registerwhichstoredprocedureshouldbecalledforinsertingnewrowsthathavebeen
addedtoaDataSet,forupdating,andsoon.)
N Noneedforaseparateinterfacelanguage(suchasIDL).
N Alllanguagesdecideonacommonsetoftypes.
N Parameterizedconstructorsandoverloading.
Thegoodnewsisthat.NETdoesdeliveronalloftheseideasand,evenyearslater,it'san
impressivelist.AlthoughMary'sdiscussionwasbasedontheideatobuildtheruntimeand
everythingelseonCOM,.NETisn'twrittenonCOM;it'sacompletelynewplatform.
Inaddition,.NETalsoincludesthefollowingfeatures:
N Remoting(forexamplewithSOAP)
N ADO.NET
N ASP.NET
N WindowsForms
N WebForms
N XMLWebservicessupport
N BaseClassLibrary(BCL)
N Anewandamuchmorecompetentversioningschema
N AnewlanguagecalledC#
N ManychangestoVB6(orratherarewrite)tomakeitVisualBasic.NET
It'simportanttonotethateventhoughthecommonlanguageruntimeisextremelyimportantin
.NET, itdoesnotreplaceCOM+.Thismeansifyourcomponentswritteninmanagedcode(thatis,
codemanagedandexecutedcompletelybythecommonlanguageruntime)needcomponent
services,suchasdeclarativetransactions,youwillstilluseouroldfriendCOM+.Wewilldiscuss
servicedcomponents(componentsthatusecomponentservices)furtherinaminute.
Intheend,youmaybeaskingyourselfwhetheritisanadvantageoradisadvantagetohaveprior
experiencewithCOMandCOM+whenyouenterthenewworldof.NET.Inmyopinion,it's
definitelyanadvantage.
.NET Component Services
.NETisn'treallyanevolution,it'sarevolution.Evenso,writingservicedcomponents(thatis,using
componentservicesin.NET)is,inessence,evolutionary.Ifyouhaveworkedwithwriting
componentsthatuseCOM+componentservices,youwillbewellprepared.
Listing1.1showswhataverysimpleservicedcomponentwouldlooklikeasaclassinVisualBasic
.NET.(Forittowork,youmustsetareferencetoSystem.EnterpriseServices.dll.)
Listing 1.1 A Simple Serviced Component in Visual Basic .NET
Imports System.EnterpriseServices
Public Class MyFirstSample
Inherits ServicedComponent
Public Function GetTime() As String
Return Now.ToString()
End Function
End Class
Whenthisclassisusedforthefirsttime,itwillautomaticallyberegisteredasaconfiguredCOM+
component,whichcomesinhandyatdevelopmenttime.Still,atdeploymenttime,thisfeatureis
mostoftennotuseful,becausetheuserrunningtheapplicationmusthaveadministratorprivileges
tohaveitregistered.YoucanuseRegSvcstomanuallyregisterthecomponentasaconfigured
COM+component.
COM+objectpoolingwillnowbeavailabletothemajorityofCOM+programmers,namelythose
who useVB6,whentheyshifttoa.NETlanguage,suchasVisualBasic.NETorC#.Anaddedplus
isthatthecomponentsyouwritewithVisualBasic.NETandC#willnothavethethreadaffinity
problems-whichmeans,forexample,thatanobjectofaVB6-written classcanonlybeusedfrom
thesamethreadastheonethatinstantiatedtheobjectbecauseoftheheavyuseofThreadLocal
Storage(TLS)-andSingleThreadedApartmentonlythreadingmodelofVB6.
Averyhandyfeatureof.NETisthattheattributescanbewrittenincode.Listing1.2showsan
exampleinwhichaclassisdecoratedwithattributesforobjectpooling.
Listing 1.2 Attributes Decorating a Class
<ObjectPoolingAttribute(Enabled:=True, _
MinPoolSize:=2, MaxPoolSize:=3, _
CreationTimeout:=20000)> Public Class MyPooledClass
Special Considerations
Youmaybewonderingwhetherthe.NETprogrammerhastoconsideranythingspecialwhen
writingservicedcomponents.Indeed,severalthingsdifferbetweenwritingordinary.NET
componentsandwritingservicedones,includingthefollowing:
N YoushouldcallDispose() whenyouaredonewithaninstanceofaservicedcomponent.
(Mostoftenit'snotamust,butrecommended.)
N Youshouldnotusenondefaultconstructors.
N Staticmethodsarenotserviceable.
N Servicesflowbetweenmachines,butonlywhenDCOMisused.(Thatmeans,forexample,
thatyoumustuseDCOMinsteadof.NETRemotingifyouwanttouseaCOM+transaction
thatspanstwomachines.)
N Duringruntime,theuserexecutingtheCOM+applicationmusthavepermissiontorun
unmanagedcode.
Asusual,consumerswon'thavetoknowwhethertheusedcomponentisservicedornot.Of
course,theconsumerscanbeunmanagedcode,suchasVB6-writtencode.Infact,using.NETto
writebetterCOM+componentsforunmanagedconsumerswilllikelybeacommonscenariointhe
nearfuture.
COM+ 1.5 Component Services
WindowsXPandWindows.NETServerwillcontainCOM+1.5andwithitalotofnewfunctionality,
including:
N Processrecycling
N Configurableisolationlevel
N ApplicationsasWindowsservices
N Memorygates
N Pause/disableapplications
N Processdumping
N Movingandcopyingcomponents
N Public/privatecomponents
N Applicationpartitions
N COM+applicationsexposedasXMLWebservices
Wewilldiscusseachofthesefeaturesintherest ofthissection.
Process Recycling
Asimplewayofescapingtheproblemswithmemoryleakageistorestarttheserverprocessnow
andthen.Withprocessrecycling,youcandeclarativelytellhowandwhenthisshouldhappen-for
example,afteracertainamountofrequestsoreverynight.
Note
IoncewroteaWebapplicationthathadamemoryleak.Thecustomerthoughtitwasa
cheapsolutionforthemtojustrebootthemachineeveryweekendbecausetheyhad
peopleworkingthenanyway.Ipreferredtotrytosolvetheprobleminsteadofusinga
Band-Aid,buttheydidn'tseethisasaproblematall.Ofcourse,processrecyclingwould
havebeenmoreconvenientforthem.
Configurable Isolation Level
InCOM+1.0,thetransactionisolationlevelwasalwaysSERIALIZABLE forCOM+transactions
againstSQLServer.(OraclegetsthesamerequesttosettheisolationleveltoSERIALIZABLE,but
neglectsthisandusesREAD COMMITTED instead.ThisisbecauseOracleusesversioninginsteadof
lockingforconcurrencycontrol,andthenREAD COMMITTED ismostoftenenough.)
3
Inthepast,this
hasbeenamajorconstrainttokeepinmindwhendesigningforCOM+,butinCOM+1.5,itwillbe
possibletoconfiguretheneededisolationlevel.
Applications as Windows Services
Someofthecomponentservices,suchasqueuedcomponents,needastartedCOM+applicationto
work.InCOM+1.5itispossibletorunCOM+applicationsasWindowsservices.Thisalsocomesin
handyifyouliketoruntheapplicationasthelocalsystemaccount,tomakeitpossibletoactwith
theprivilegesfromthatuser.Thisisalsoasolutiontotheshortdelaythatmightotherwisebe
apparentwhenthefirstrequestismadetoaCOM+serverapplicationaftersystemboot(orafter
theapplicationhasbeenshutdown).
Memory Gates
TheMemoryGatesfeaturepreventsthecreationofobjectswhentheamountoffreememoryfalls
belowacertainlevel.Thereasonforthisfunctionalityistoavoidaddingobjectswhenthere'snot
enoughmemorytosupporttheiroperations.Strangeerrorsoccurwhenasystemrunsoutof
memory,anditishardtotrapforerrorsincomponentsinthisenvironment.Infact,most
componentsaren'ttestedforlowmemoryconditions,soitisbetterthattheydon'tgetcalledatall
ifthesystemhasrunoutofmemory.
Pause/Disable Applications
InCOM+1.0,youcan'tpauseaCOM+application.Instead,youhavetodeletethecomponentif,
foranyreason,youdon'twantanybodytouseitforacertainamountoftime.InCOM+1.5,you
canbothpauseanddisableapplications.
Process Dumping
TomakedebuggingeasierinCOM+1.5,itwillbepossibletotakeasnapshotoftheprocess
withouthavingtostoptheprocess.Thissnapshotcanthenbeusedwithadebuggertoexamine
theexactstatewhentheproblemoccurred.
Moving and Copying Components
Often,itisusefultobeabletoletthesamecomponentappearseveraltimes,inseveral
applications.Thatisespeciallythe caseforhelpercomponents.Assumethatonehelpercomponent
isusedbyfivedifferentCOM+applications.InCOM+1.0,allfiveapplicationshavetobeupgraded
atthesametimetousethenewversionofthehelper.Igenerallyprefertoupgradestep-by-step
instead.Therefore,it'scommonthathelpersarereusedinsource-codeforminsteadofasbinary
components.InCOM+1.5,itwillbepossibletoconfigurethesamephysicalcomponentseveral
timesandindifferentways.
Public/Private Components
InCOM+1.0,allcomponentsarepublic.InCOM+1.5,itwillbepossibletosetacomponenttobe
privatetothecurrentapplication.Thelessyouexposeexternally,theeasieritisforyoutomake
changesinthefuturetotheapplication.
Application Partitions
Thepurposeofapplicationpartitionsistopartitionamachinesothatapplicationsdon'tdisturb
eachother.Atypicalsituationwhenapplicationpartitionsareofgreatuseisindatacenters.
ConsiderthepossibilityofrunningseveralinstancesofSQLServer2000inonesinglemachine.
COM+ Applications Can Be Exposed as XML Web Services
IfyouwanttoexposesomefunctionalityofaCOM+applicationasXMLWebservices,COM+1.5
makesthateasy.YoucanachievethatautomaticallywiththehelpoftheComponentServices
Explorer.
Note
NowthatIhavesoundedlikeasalespersonforawhile,I'dliketosaythatIwastoldby
MicrosoftthattherearenoplanstoaddthefunctionalityofCOM+1.5toWindows2000.
Onlytimewilltell.
Visual Basic .NET
I,forone,havebeenlongingforVisualBasic.NETforalongtime.Why,youask?
Problems Solved with Visual Basic .NET
VBhasalwayshadgreatideasforhowapplicationsshouldbebuilt,buttheimplementationshave
hadquirks.ThefollowingareafewexamplesofproblemsthatwerefoundinVB6butthatarenow
solvedinVisualBasic.NET:
N WithEvents can't be used with user-defined interfaces- ThefirsttimeItriedtouse
WithEvents withuser-definedinterfaces,ittookmehalfadaytodecidethatitjustwasn't
possible.Acall-backsolutionsolvestheproblem,butit'sstillirritatingthatthesystem-
pluggedsolutiondoesn'twork.
N Single-threaded apartments (STA) are not always a sufficient model for multithreading-
VB6protectsusfromourselves.Mostofthetime,thisisagoodthing.Ontheotherhand,
whenwehaveadvancedneedsandknowwhatwearedoing,itwouldbenicetohavethe
capabilitytomoveintodangerousgrounds.Usingfreethreadingisatypicalexample.Thisis
definitelyinterestingforCOM+components.Thebuilt-insynchronizationsupportinCOM+
helpsalotwithwritingthread-safecodewithoutSTAs.
N When GlobalMultiUse is used, the class must live in another DLL- Ihavealsohad
problemswithGlobalMultiUse inCOM+applications. Therefore,Iinsteaduseold-style
codemodulesformyhelperroutinesinVB6.ThatmeansIoftenemploycodereuseinstead
ofbinaryreuse,butIalsoskipsomeinstantiationoverhead(bothwhenIwritethecodeand
whenthecodeisexecuted).
N The class editor isn't made for writing user-defined interfaces, and VB creates an ugly type
library- Becauseofthis,althoughitispossibletotakesomecontroloftheinterfaceswith
VB6,seriousVB6developersoftenwritetheinterfacesinIDLandcompilethemwith
MicrosoftInterfaceDefinitionLanguage(MIDL,thecompilerthattransformsIDLinto,for
example,atypelib).Thereisnotalotof"VBfeelinginthatapproach,butitisthe
preferredway.
N There is a lack of strong typing- IfIforgettodeclare thedatatypeforavariableor
parameter,Iconsiderthisabug,andIdefinitelywantthecompilertotellmeaboutit.VB6
forgivesandoffersaVariant instead.AnotherexampleisthatVB6forgiveswhenIforget
todoanexplicitcast.Itmakesanimplicitoneautomatically.Isitgoodtobeforgiving?No,
notinthiscase.Hidingrisksandbugsisnotagoodthing.
N There is a weak error-handling scheme- TheerrorhandlinginVB6isquiteweakand
definitelynotwellunderstood.Unfortunately,it'snotuncommontofindinsufficientor
incorrecterror-handlingcodeinarticlespublishedbyMicrosoft,either.
N The implicit cleanup is shaky- Especiallyinahighlystressedserver-sidescenario,there
havebeenseveralreportsthattheimplicitcleanupofVB6isn'tcleaningupcorrectly.There
isalsothemorewell-knowncircularreferenceproblemofCOMthatyouhavetobreakup
manually.(Tounderstandthecircularreferenceproblem,assumethatyouhavetwo
objects,eachofwhichkeepsareferencetothe otherobject.It'snotenoughtosetoneof
thoseobjectstoNothing explicitly.Thereferencecounting-basedcleanupalgorithminCOM
willkeepbothobjectsalive.)
N There is a lack of shared (or class or static) members- Itriedtoimplementanordinary
Singleton
4
(whichmeansonesingleobject,reusedoverandoveragain,similartoaglobal
object)withoutanyluckwhenVB4wasreleased,becauseVB4didn'thavemembersthat
belongtotheclassratherthantheobjects.ThesamegoesforVB5andVB6.
Inadditiontotheseproblems,therehasalsobeenalackofotherobject-orientedconceptstoo.
Themostcommoncomplaintinthatareaisprobablythelackofimplementationinheritance.
(Implementationinheritancemeansthataclasscaninherittheimplementationfromanotherclass.
Whenaprogrammerrefersto"inheritance,heorsheisprobablyreferringtothistypeof
inheritance.)Atfirst,IthoughtMicrosoftwas"creatingatruthwhentheysaidthatlargeprojects
wouldsufferifyouusedimplementationinheritance.(Overtheyears,Ihavedevelopedanability
toseeconspiracieseverywhere.)Afewyearslater,IreadClemensSzyperski'sComponent
Software: Beyond Object-Oriented Programming
5
andIunderstoodthatitwasawell-knownfactin
academicresearch.Sincethen,I'vebeenhappyusinguser-definedinterfacesandseparatingthe
interfacefromtheimplementation.Iwilluseuser-definedinterfacesalotin.NETtoo,butthereare
situationsforimplementationinheritance.Inheritanceisgoodifyouwanttoinheritthe
implementation fromthebaseclass,butthatisactually theonlycase.Wealsoonlyhavesingle
inheritancein.NETand,therefore,youhavetobecarefultonot"wastethesingleinheritance
possibility.
AsImentionedearlier,theseproblemshaveallbeensolvedwithVisualBasic.NET.VisualBasic
.NET=theVBway,withfewerquirks.
New "Problems" with Visual Basic .NET
Unfortunately,eventhoughVisualBasic.NETsolvesalotofproblems,itintroducessomeaswell.
Forone,VisualBasic.NETisamuchharderlanguagetolearnfornewcomersthanVB6.Itismore
competent,butalsofullofadvancedconcepts.Formaltrainingand/ormuchexperienceareamust.
Infact,forthisreasonandothers,somedevelopersdon'tlikeVisualBasic.NETatall.Inmy
opinion,it'sahugeandgreatleapforward.Onethingthatdoesirritatemeisthatbetweenbeta1
and2,MicrosoftsuddenlyannouncedthattheywerechangingVisualBasic.NETsothatiswas
moreconsistentwithVB6.Theybasedthisdecisiononthefeedback(or,rather,criticism)theygot
fromtheVBMVPs. WhenIdiscussedthiswithMicrosoft,Iwastoldthatthesechangeswouldonly
increasebackwardcompatibility,andthatthiswouldn'taffectthefutureatall.Idon'tagree.We
willhavetolivewiththosemistakesalongtime.
ThefollowingarethechangesMicrosoftmadebetweenbeta1andbeta2ofVisualBasic.NET:
N True should be represented as -1 in numerical form, not as 1- Inmyopinion,ifyouhave
codedsothatyouaredependentonthenumericalrepresentation,youshouldbeprepared
tomakechangesinthefuture.
N And and Or will not be short-circuiting- Themainriskwassaidtobeifauserhadwritten
anIf clauseasfollows:
N
N If a = 1 And InitializeSome() Then
AssumethatInitializeSome() setssomeglobalvariables.IfAnd wouldbeshort-circuiting
anda = 1 isFalse,InitializeSome() wouldnotexecuteinVisualBasic.NET.Inmy
opinion,thistypeofcodingisahack,andtheprogrammershouldbepreparedforuglycode
likethistobreakinthefuture.Itwon'thappeninVisualBasic.NET.
Ifyouwantashort-circuitingAnd/Or,youshoulduseAndAlso/OrElse instead.Ialways
wantittoshortcircuit,soIhavetogetusedtousingthosenewsyntacticelements.
N And/Or will be bitwise operations again, as in VB6- (Beforethischange,theywerepurely
logicaloperatorsandBitAnd/BitOr hadtobeusedforbitwiseoperations.)
N Array-declarations should set the upper bound, not the number of elements- Ifyou
declareanarrayasfollows:
N
N Dim x(5) As Integer
yougetanarrayofsixelements,notfive.Overtheyears,thishasirritatedmerepeatedly.
It'snotintuitive,andIcan'tseethepointindoingitlikethis.
Despitethesechanges,theMVPsarestillunhappywithVisualBasic.NET.TheonlythingMicrosoft
achievedwastomakethelanguage weakerandtoirritatealargeandquietpartoftheVB
community.
IdecidedonVisualBasic.NETasmy.NETlanguageofchoicequiteearly.Afterthat,when
Microsoftdidtheirmovementback,IalmostdecidedtouseC#instead.It'sactuallynotadramatic
decision.YoucanbeswitchingbetweenVisualBasic.NETandC#,andstillbequiteproductive.I
willprobablyworkmostlyinVisualBasic.NET,despitesomeirritatingdetails,becauseofmy
background.I'msurethatI'lluseC#quitealotaswell.
C#
AlthoughsomewouldarguethatC#isJava-based,itisprimarilyrootedinC++.Therehavebeen
somerestrictionsandalotofcleanupinthelanguageinthetransition.C#hasalsobeenVB-ified
toalargedegree,whichmadeitsimplerandmoreproductivetousethanitsancestorC++.
Althoughtheydodiffer,C#andVisualBasic.NETareverymuchalike.Ifyoumasterone,it'squite
easytousetheother.AlotofyourtimeinvestmentwillbeputintheBaseClassLibrary(BCL)and,
becauseitisshared bybothC#andVisualBasic.NET,youdon'thavetospendtimerelearningits
concepts.
C# Versus Visual Basic .NET
ThedifferencesbetweenC#andVisualBasic.NETaremostlytosuitdifferentprogrammer's
backgrounds.Unfortunately,therearesomeother differences.Thefollowingisalist(otherthan
thosethatwerementionedwhenIdiscussedMicrosoft's"goingback)ofsomeexamplesofthe
differencesbetweenC#andVisualBasic.NET:
6
N C# warns you if a function is missing a return statement- Thiscatchesafewbugs
automatically.
N C# requires XML-tagged documentation to be written in the code- Seemstobeagood
ideatodragoutthecommentsfromthecodeanduseitfordifferentscenarios.
N C# can switch to Unmanaged mode- ThiscaneasilybeachievedinVisualBasic.NETby
justcallingaC#classthattakescareofit.Still,Ifinditnicetohavethesamepossibilities
inbothenvironments.
N You can use Using() in C# to tell that an object should be automatically Dispose()ed when
going out of scope- Thatisveryhandy,forexample,forconnectionsandfileobjectsthat
youdon'twanttowaitforbeinggarbagecollected.
Tobefair,VisualBasic.NEThassomeadvantagesoverC#too:
N Visual Basic .NET differs Inherits from Implements- InC#,it'swritteninbothcaseswith
acolon.
N The event syntax is simple in Visual Basic .NET- It'sascleanandintuitiveasinVB6.In
C#,youhavetotakecareofmorebyhand.
N Visual Basic .NET is not case sensitive- Thisisamatteroftaste,butIprefernon-case
sensitive.Otherwise,thereisalwayssomebodythatwillhavetwomethodsinasingleclass
namedsomethinglikegetData() andGetData().
Note
DespitethedifferencesbetweenC#andVisualBasic.NET,youcanusethisbookwith
eitherC#orVisualBasic.NET.Thebookisprobablymore"directifyouhaveyour
backgroundinVB6,becausethatiswhatIhavetoo.IthinkandreasonmoreasaVB
programmerthanasaC++ programmer.
SQL Server 2000
ComparedtoVisualBasic.NET,SQLServer2000isalmostanold-timer.StillIthinkit'simportant
todiscusssomeofitsnewfeatures.AlthoughyoumaythinkthattheleapfromSQLServer7to
2000wouldbeabigone,therealchangesaren'tallthatdramatic.However,therearesome
interestingfeaturesfordeveloperstoconsider,includingXMLsupport,user-definedfunctions,and
distributedpartitionedviews,amongothers.
XML Support
I'mageek,andsoIlovetousenewtechnology.WhenXMLcameonthemarketacoupleofyears
ago,Iuseditasthefileformatinaprojectforacustomerofmine.(Hey,Iknowwhatyouthink,
butmycustomeractuallythought-andstillthinks-itwasagoodsolution!)However,Isoon
learnedthatXML-especiallyitstoolsupport-wasnotthewondertechnologyIhadfirst
envisioned.Theprojectworkedoutfineintheend,butIbecamereluctanttouseXMLforsome
time.Sincethen,thestandardsandtoolsupportforXMLhavegonethroughtremendous
development,andaresuretocontinueintothefuture.
Ibecamere-interestedinXMLwithSQLServer2000.ThereisalotofXMLsupportinSQLServer
2000.Thefollowingarethemostinterestingfeatures:
N You can now pass XML documents to stored procedures and open them for further
processing- Thatgives,forexample,agoodsolutionfortheproblemofhandingover
severalrowstoastoredprocedureinonecall.
N You can fetch results from SELECT statements as XML documents- Insomesituations,it
will beagoodsolutiontocreatetheresultinXMLdirectlyatthedatabaseserverinsteadof
convertingitatthemiddletierorattheclient.
PerhapstheXMLsupportisthereasonthatSQLServer2000nowiscalleda.NETserver.It'snot
writteninmanagedcodeandnothinghaschangedsinceitonlywascalledSQLServer2000,but
hey,itcantalkXML!WhatcanIsay?Marketers.
User-Defined Functions
InSQLServer2000,wefinallyhadUser-DefinedFunctions(UDFs).Sincewehavewaitedsolong,
MicrosoftwaskindenoughtogiveusthreedifferentversionsofUDFs:
N Scalar Functions- Thesecanbeusedforcreatingfunctionstobeusedthesamewayas
columnsinSELECT statements,similarto,forexample,CONVERT().
N Inline Table Valued Functions (ITVFs)- ITVFsaresimilartoviews,butcantake
parameters.TheresultiscreatedwithonesingleSELECT statement.
N Multistatement Table Valued Functions (MTVF)- Inthiscase,theresultcanbecreatedwith
multiplestatements.MTVFsare,forexample,agoodsolutioninsituationswhereyou
normallyusetemporarytables.
TheUDFimplementationinSQLServer2000isatypicalfirstversion,withseveralquirks.For
example,itisnotpossibletouseRAND() andGETDATE() inMTVFs.Thesamegoesfortouching
temporarytablesandcallingstoredprocedures.Microsoftwillprobablyfixsomeoftheroughedges
inanupcomingversion.
Distributed Partitioned Views
Withdistributedpartitionedviews(DPVs),atablecanbepartitionedoverseveralmachinesandit
willbecompletelytransparenttotheclients.ThisiscurrentlythetechniqueMicrosoftisusingto
achieveshared-nothingclusteringwithSQLServer.
Note
IwilldiscussDPVsmoreinChapter5,"Architecture.
Other Interesting New Features
ThefollowingaresomeothernewfeaturesofSQLServer2000,ofhighinteresttodevelopers:
N Improved replication support- ItisnowpossibletousequeuedupdateswithMSMQ.In
addition,mergereplicationdoesn'thavetobereconfiguredfromscratch,forexample,every
timeyouneedtochangethestructureofatable.
N BIGINT and TABLE data types- TheordinaryINT (32bits)issometimestoosmalladata
type.Thetypicalexampleisforprimarykeys.Fourbillionrows(assumebothpositiveand
negativevalues)aren'tenoughinmanycases.BIGINT (64bits)willprobablycomeinhandy
inthesesituations.TheTABLE datatypecanbeusedinsteadofanordinarytemporarytable
tocreateatemporarytableinmemoryinsteadofinthetempdb.Bydoingthis,youcansave
overhead.ThisdatatypeisalsoimportantfortheUDFs.Unfortunately,theTABLE datatype
can'tbeusedasaparameterbetweenstoredprocedures.Anotherlimitationisthatastored
procedurecan'tbethesourcewhenaddingrowstoavariableofTABLE type.
N Declarative cascading referential integrity- PeoplehavebeenwaitingforthisSQL92
featurealong time.
Note
Inmyapplications,declarativecascadingreferentialintegrityisn'tveryimportant.I
prefertakingcareofthisexplicitlyinthestoredproceduresthatareresponsibleforthe
deletions.IwilldiscussthismoreinChapter7,"BusinessRules.
N INSTEAD OF triggers- INSTEAD OF triggersexecuteinsteadofaspecificUPDATE operation,
forexample(inotherwords,theoriginalUPDATE willnotexecute).Thisistheonlykindof
triggeryoucanuseonaview.
N Multiple instances- It'spossibletohaveseveralSQLServerinstancesonasinglemachine.
ThisisgreatfortestinganapplicationonseveralversionsofSQLServer,onasinglebox.It
isalsoveryusefulinconsolidationscenarios.
N Extended properties- Extendedpropertiesmakeitpossibleforyoutoaddyourown
metadatatotheschema,such asaddingdocumentationforeachcolumnineachtable,
storingeditmasks,andsoon.
N Indexed views- Anindexedviewwillhaveanindexofitsownandnotonlyastoredquery
definitionaswithanordinaryview.Thiscangreatlyenhanceperformanceforsomequeries.
N New built-in functions- TheseincludeGETUTCDATE() andSCOPE_IDENTITY(),whichwill
returnthelastIDENTITY valuethatiscreatedinthecurrentscope,suchasthestored
procedure.Thisway,theproblemwith@@IDENTITY,inwhichatriggercouldhaveaddeda
rowtoanothertablewithanIDENTITY thatchangedthevalueof@@IDENTITY,isavoided.
(Onceagain,ifyoudon'tusetriggers,thisisnotaproblemanyway.)
N Save TEXT,IMAGE, and so on in row value- Quiteoften,thedataforaTEXT orIMAGE
columnisquitesmall.Inthesecases,itwouldbebeneficialfromaperformancepointof
viewtostorethatdatawiththerestoftherow,insteadofinitsownstructure,aswas
alwaysthecaseinpreviousversionsofSQLServer.
N Several productivity enhancements in Query Analyzer- Theseincludesupportfor
templates,objectbrowser,andbuilt-indebugging.
And,asistriedwitheveryversion,therehavebeenalotofimprovementswhenitcomesto
performanceandscalability.Oneexampleisthateachconnectionisnowconsuminglessmemory
thanbefore.
Outstanding Problems with SQL Server 2000
Unfortunately,theT-SQLprogramminglanguagestillfeelsweakandold-fashioned.Thefollowing
aresomeofthebasicproblemsitstillhas:
N Youcannotdirectlydefineordinaryconstants.
N Youcan'tusestatements(suchas@x + @y) asparameters.
N Youcan'tdirectlysendarraysasparameters.(TheXMLsupportinSQLServer2000isone
possiblesolutionthatpartlysolvestheproblem.IwilldiscussthisfurtherinChapter8,
"DataAccess.)
N Theerrorhandlingisveryold-fashionedandhasmanyquirks.
N Therearesomehindrancesforcentralizingcode.
Beyond SQL Server 2000
Therehasn'tbeenmuchtalkaboutthenextSQLserverversionyet,code
namedYukon,butthefollowingarethefeaturesthatI'veheardofsofar:
N ThepossibilitytostoreXMLdocumentsincolumnsandbeingableto
indexthecontent
N BetteradministrativesupportforDPV
N Thepossibilitytousethe.NETlanguagestowritethe"stored
procedures
N ThepossibilitytoletSQLServerstorethefilesystem
Timewilltellwhichorallofthesefeaturescometofruition.
What's Next
Inthenextchapter,IwilldiscussthefactorsIwillusetoevaluatemyproposalstokeyproblems
thatwewilldiscussthroughouttherestofthebook.
References
1.Microsoft Systems Journal,November1997,http://www.microsoft.com/msj/1197/complus.htm.
2.Microsoft Systems Journal,December1997,
http://www.microsoft.com/msj/1297/complus2/complus2.htm.
3.T.Ewald.Transactional COM+: Building Scalable Applications.NewYork:Addison-Wesley;2001.
4.E.Gamma,R.Helm,R.Johnson,J.Vlissides.Design Patterns: Elements of Reusable Object-
Oriented Software.NewYork:Addison-Wesley;1995.
5.C.Szyperski.Component Software: Beyond Object-Oriented Programming.NewYork:Addison-
Wesley;1997.
6.D.Appleman.Visual Basic.NET or C#? Which to Choose?,
http://www.desaware.com/Ebook2L2.htm.
Chapter 2. Factors to Consider in Choosing a SoIution to a
ProbIem
IN THIS CHAPTER
N TypeofConsumer
N PhysicalRestrictionsandPossibilities
N Performance
N Scalability
N Other"-abilities(SuchasMaintainability,Reliability,Reusability,Testability,Debuggability,
andInteroperability)
N SecuriKty
N Farm- andCluster-Enabling
Ifyou'relikeme,yousometimesgetthefeelingthatmostcustomersthinkthattheonlyfactorto
considerwhendesigninganapplicationisrawperformance.Whenyouthinkaboutthismoreand
discussitfurtherwithyourcustomers,however,yousoonrealizethatit'snotassimpleasthat.
Afterall,whyelsewouldsuchalargepercentageoftheworld'sdevelopersuseVBintheirwork
whenit'spossibletowritefasterprogramsinassemblylanguage?AndwhyareOracleandSQL
Serversuchpopulardatabaseplatforms?Memoryallocationsaremuchfaster,asiswritingtoraw
files.Theanswerissimple:Therearealwaysseveralfactorstoconsiderwhendesigningan
application,andit'samatterofgiveandtakebetweenthem,becauseyoucan'toptimizeforthem
all.
Startingwiththenextchapter,Iwillbediscussingseveralproblemsyoucanencounterwhen
buildingenterprisecomponents.Ineachexample,Iwilldiscussseveraldifferentproposalsfor
solutionsandgiveargumentsforandagainsteachofthem.Inthischapter,Iwillpresentand
describethefactorsIwillusethroughoutthebooktoevaluatetheadvantagesanddisadvantages
ofthesedifferentsolutions.ThisshouldhelpyouseehowIhavedecidedonmypreferredsolution.
Inexplainingmyreasoning,Ihopetomakeiteasierforyoutomakeachoice,dependingonyour
uniqueenvironment,situation,andpersonaltaste.
Asyoureadthischapter,youwillfindthatthefactorsthatmustbeconsideredinmakingyour
decisionsareabitlikeapplesandoranges(orapplesandpears,aswesayinSweden).Ioftenfind
ithardtocategorizethesefactors,becausetherearealwaystoomanydimensionsthatseem
important,andanysimplemethodofcategorizationfails.Inaddition,mostdeveloperswillhave
theirowndefinitionofeachofthesefactors,aswellastheirownmeansofcategorization.
However,thinkofmycategoriesmerelyasameanstoorganizetheevaluationsinthisbook.
Portability
Althoughitmightseemstrange,I'mnotgoingtodiscussportabilitymuch
inthisbook.Thisisbecausethisbookfocusesonhowtosolvekey
problemswhendevelopingenterprisesystemsusing.NETComponent
Services,VisualBasic.NET,andSQLServer2000.Asyouread,youwill
alsofindthatthereisalotofemphasisonstoredprocedures.
Atthetimeofwriting,.NETisdesignedfortheWindowsOS.Although
therehavebeensomerumorsandannouncementsaboutsupportfor.NET
onotheroperatingsystems,nothingconcretehasbeendeveloped.And
whenitcomestoSQLServerstoredprocedures,therearen'tevenany
rumors.Okay,T-SQLinMicrosoftSQLServerlooksalotlikeitdoesin
SybaseSQLServer,buttherearenumerousdifferences.Ifyouchoose
thoseprogramsthatthebookisfocusedon,portabilityisn'tyourmain
concern,atleastnotifyouliketotakefulladvantageoftheplatform,
whichiswhatIintendtodo.Thus,portabilityisn'tafactorIconsider
whenevaluatingmyproposalsinthisbook.
Type of Consumer
Iusedtorefertothefirsttierinn-tierarchitecturesasthepresentationtier.However,thistermis
abittoonarrow.Afterall,whataboutbatchprocessesandXMLWebservices,forexample?Ithink
abettertermis"consumertierratherthanpresentationtier.Dependingonwhichconsumertype
youplantouse,yourdesignwillhavetoadapttoit.AlthoughIbelievethegeneralruleshouldbe
toletyourdesignbeasconsumer-independentaspossible,inreality,you'llhavetomakeatleast
somedecisionsbasedontheconsumertype.WhenIspeakofspecifictypesofconsumer,I'm
referringtowhethertheconsumerofyourcomponentswillbe,forexample,WebForms,Windows
Forms,XMLWebServices,orjustothercomponents.However,whileit'simportanttokeepthe
typeofconsumerinmind,don'tlooksolelyatthecurrentsituation.Itcan,anddoes,change
rapidly.Bepreparedforthesechangesasyoudesign.
Need for Coarse-Grained Methods
Tofurtheremphasizethenotionthattheconsumertypesofyourclientcanchangequickly,it's
importanttokeepinmindthatthedesignshouldbeusablebyalltypesofconsumers.However,
it'softenpossibletomakedesignchangesthatareoptimizedforaspecificconsumertype.One
good exampleofthisistheneedforcoarse-grainedmethodsexposedtotheconsumer.Assume
thatyouhaveacomponentthatacceptsanordertobecreated,productstoberequested,and
shipmentinformationtobehandedover,andthatthisorderwillbesaved.(Let'sassumethatitis
onlytheSave() methodthatwillneedadatabasetransactioninthisexample.)Eveninthis
simplifiedversion,therearefourdifferentmethodsusedand,becausetheusertypicallyrequests,
say,10differentproducts,youwillhave13methodcallsforeachorder.Figure2.1demonstrates
howthecallstackmightlookwhenafine-grainedcalldesignhasbeenused.
Figure 2.1. Interaction diagram showing a fine-grained call design.
IfWebFormsistheconsumer,atypicaldeploymentschemawouldbeonthesamemachineasthe
components.IfthecomponentsaretoliveinaCOM+LibraryApplication(whichmeansthatthe
componentswillexecuteinthesameprocessastheASP.NETapplication),havingsomanymethod
callswillcarryquitelowoverhead.YoucanseeanexampleofthisdeploymentschemainFigure
2.2.
Figure 2.2. Deployment schema for when Web Forms are the consumer.
InthecaseofWindowsFormsastheconsumer,eachmethodcalltotheapplicationserveris
costly,becausethecomponentI'mdiscussingheretypicallylivesattheserver,whiletheWindows
Formslivesattheclientcomputer.Thereishighoverheadinthiscase.(Thesolutioncouldmean
thatsomefaulttoleranceisbuiltinsothat,iftheclientgoesawayprematurely,theservercould
rememberthestateuntiltheclientgetsback.)Youcanseeanexampleofthisdeploymentschema
inFigure2.3.
Figure 2.3. Deployment schema for when Windows Forms are the consumer.
TherewillalsobemuchoverheadifwecreateanXMLWebservicemoreorlessdirectlyoutofthe
Order component.Then,theconsumeroftheXMLWebservicewillhavetomakeseveralcallsto
theXMLWebservicetohavetheservicefulfilled,inthiscaseovertheInternet,whichcanbeslow
andunreliable.
AsimpleandtypicalsolutionwouldbetoaddaFacade
1
tohelptheWindowsFormsandXMLWeb
servicesbyprovidingasinglemethod(tobecalledonceforacompleteorder)thatwilltakecareof
makingthe10fine-grainedmethodcalls.Bydoingthis,youcreateasolutionthatisoptimizedfor
WindowsFormsandXMLWebservices,butWebFormsdoesn'tgainmuch.Whiletherearesome
drawbackstotheconsumer,suchasalittlelessintuitiveinterfacetouse,therearealsosome
advantages,suchasdeploymentflexibility.TheapplicationcouldbedeployedasaCOM+server
application(inaprocessofitsown)insteadofasaCOM+libraryapplicationwithoutterribly
degradingperformance.InFigure2.4,youcanseethecallstackforthesameorderexample
again,thistimeusingaFacadetogetacoarse-grainedcalldesigninperspectiveoftheconsumer.
Figure 2.4. Interaction diagram showing how a Facade can solve the problems
associated with fine-grained call design.
Concurrency Control
Let'stakeasecondexampleinwhichthechosenconsumertypewillaffectthedesignof
concurrencycontrol.(Withconcurrencycontrol,Imeanamechanismthatdoesnotallowdifferent
transactionstoaffecttheresultoftheothertransactions,eventhoughtheyruninparallel.The
resultshouldbethesameasifthetransactionshadbeenexecutedafteroneanotherinsteadofat
thesametime.)Assumethatyouwanttouseoptimisticconcurrencycontrol,whichisthatyou
expecttobeabletoupdatearoweventhoughyoudon'tkeepalockontherowanddisconnect
fromthedatabasebetweenthefetchfromthedatabaseandwhenit'stimeforupdating.One
typicalsolutionwouldbetorememberaROWVERSION value(calledTIMESTAMP inversionsbefore
SQLServer2000)foundatthefetchoftherowuntilit'stimetosavetherow.TheUPDATE
statementmightthenlooklikethecodefoundinListing2.1.
Listing 2.1 UPDATE Where ROWVERSION Is Used
UPDATE ordermaster
SET deliverycountry = @deliveryCountry
, deliveryaddress = @deliveryAddress
WHERE rv = @rv
AND id = @id
NoteinListing2.1thatif@@ROWCOUNT is0 and@@ERROR is0,someoneelsehasprobablychanged
therow,sincewefetchedit.
AnothertypicalsolutionwouldbetoreusethevaluesthatwerefoundatreadtimeintheWHERE
clause,asisdemonstratedinListing2.2.NotethatthevariableswithOld asasuffixarethevalues
foundintheread.
Listing 2.2 UPDATE Where "Complete" Comparison Is Used
UPDATE ordermaster
SET deliverycountry = @deliveryCountryNew
, deliveryaddress = @deliveryAddressNew
WHERE deliverycountry = @deliveryCountryOld
AND deliveryaddress = @deliveryAddressOld AND id = @id
TherearesomeadvantagestothesolutionfoundinListing2.2.Forexample,ifsomeoneelse
changesatotallydifferentcolumn, itwon'tinterferewithourUPDATE.Assumethatyouusea
DataSet fromaWindowsForm.YoucanthenkeepaninstanceoftheDataSet intheWindows
FormandmakeallthechangestotheDataSet.Whenit'stimeforupdating,theDataSet couldbe
senttothecomponentthatwilltakecareoftheUPDATE.IntheDataSet,boththeoldvaluesand
thenewvaluesarefoundandcanbeusedforcallingthestoredprocedurethatwilltakecareofthe
UPDATE foreachrow.
Note
InChapter8,"DataAccess,IwilldiscusshowIaccessthedatabase.Thereyouwillsee
thatinsteadofdirectlyusingSqlDataAdapter,Iuseacustomhelper.
IwilldiscussconcurrencycontrolindepthinChapter9,"ErrorHandlingandConcurrency
Control,whereIwillalsoshowotherpossibletechniques.
Ontheotherhand,whenitcomestoWebFormsandXMLWebservicesasconsumers,itisless
probablethattheconsumerwillkeeptheDataSet betweenthefetchandtherequestforUPDATE.
Instead,theDataSet istypicallyreleasedafterthevalueshavebeenusedfor renderingtheWeb
FormpageorreturningtheresultfromtheXMLWebservice.Whentheuseraskstosavesome
changes,perhapsanewDataSet isfetchedfromthedatabase.Thechangesarethenmadetothat
DataSet.However,inthisDataSet,thereisabigchanceyouwillfetchvaluesdifferentfromthe
firstfetch;theprotocoldoesn'tworkhere.Instead,theoriginalvaluesmustbekeptsomewhere
elseuntilit'stimefortheUPDATE.
Finally,assumethatyouhave25columnstoUPDATE-notanunheard-ofsituationatall.Thiswill
createalotofworkandrequiremorememorythanusingasingleROWVERSION.
Physical Restrictions and Possibilities
Ofcourse,asI'msureyouareaware,itisextremelyimportanttokeepthe
physicalrestrictionsinmindregardingyourdesigndecisionswhenyouallocate
responsibilitiestodifferentlayersandtiers.Ontheotherhand,aswasthecasein
consideringconsumertype,it'simportanttonotlooktoonarrowlyatthecurrent
situationinthiscaseeither.Ifyouuseanew, morepowerfulserverforthedata
tierinsteadofanoldtiredone,thesituationhaschanged.Thebeststrategyisto
trytoputtheresponsibilitieswheretheybelong:tomakethemostsenseinthe
mostcommoncase,notonlywheretheywillgivethebestresourceutilizationin
thecurrentconfiguration.Thisisespeciallytrueifyou'rebuildingsomethingthat
youplantoselltoseveralcustomersandyoudon'tknowforsurewhat
configurationtheothercustomershave.
Still,whatisconsideredagooddesigninthemostcommoncasecouldgive
painfullyslowresponsetimesinaspecificconfiguration.Assumethatyoubuilda
systemforacompanythathasitsofficesspreadover10differentcitiesallover
thecountry.AprettyslowWideAreaNetwork(WAN),let'ssay256KB,connects
theoffices.Anewapplicationistobebuiltandarichclientenvironmentis
needed.Ithasalsobeendecidedthattherewillbeonlyonedatabaseserver,
whichwillbelocatedatonespecificofficewherethereisahugeamountoftraffic.
Inaddition,eachofficehasaLocalAreaNetwork(LAN)andanapplicationserver
runningtheservercomponents.
Perhapsyouarewonderingwhatthereasonforhavingonlyonedatabaseserveris
insteadofhavingonedatabaseserverateachlocationandusingmerge
replication.Assumethattheapplicationhasonequeueofrequestsfromwhich
eachofficeworkerwillchoosearequestwithwhichtowork.Theywill,sotosay,
competefortherequests,andthereisnogeographicalororganizational
categorizationofrequests.(Thisway,loadbalancingofworkiseasilyachieved.)
Thisalonewillmakeitdifficulttoreplicatethedatabaseoverseveraldatabase
servers.
ThisconfigurationisschematicallydepictedinFigure2.5.Asyoucansee,
ApplicationServers2-n areconnectedtothedatabaseserverviaaWAN,while
ApplicationServer1isconnectedviaaLAN.
Figure 2.5. Sample configuration of n application servers connected to one database
server.
WhenconnectinganapplicationservertothedatabaseserverviaaWAN,there'sa
performancepenaltywhenhittingthedatabasewithmanysmallcalls.
Note
Aroundtripis,forexample,arequestsentfromtheapplicationserverto
thedatabaseserverandtheanswergoingbackagain.Iwilldiscussthe
importanceofandhowtoreducethenumberofroundtripsindepthin
Chapters5,"Architecture,and8,"DataAccess.
Fromaperformanceperspective,it'snormallypositivetocheckthedata-focused
businessrulesinthedatabasetier. Inthiscase,assumethatasmuchas20%of
theUPDATE requestsfailbecauseabusinessruleisnotmet.Itwouldprobably
thenbedisadvantageoustohavethebusinessrulescheckedinstoredprocedures
inthedatabase.Alotofthescarcebandwidthwillbeconsumedwithoutanyuse.
AsIhavesaid,Ibelieveagoodstrategyistoputresponsibilitieswheretheymake
mostsenseinthemostcommonsituation.Ifyouneedtooptimizeforcertain
otherconfigurations,then,ofcourse,it'sniceifyoumakeitconfigurablesothat
differentcustomerscanconfiguretheapplicationfortheirspecificsituations.
Let'stakeanotherexampleofaphysicalrestriction.Client-sidecachingofstatic
dataisnormallyverybeneficial-bothfromtheperformanceandthescalability
perspective-becauseyoucansavenetworkroundtrips.However,iftheclient
computershaveashortageofmemory,it'snotagoodideatousealotof
memory-consuming,client-sidecaching.
Performance
AlthoughMoore'sLawstatesthatcomputingpowerwillbedoubledevery18
months,moreoftenthannot,performanceisstillaveryrealproblem.Why?There
isn'tonesingleanswertothatquestion.Possiblereasonscouldbe
N Users'expectationsforfunctionalityandinteractivityaregrowingallthe
time.
N Designs,tools,andlanguagesareexchangingefficiencyforproductivity.
Hardwareisbecomingcheaperallthetime;consultants'hourlyfeesarenot.
N Networks(bothfastandslow)aremoreoftenacomponentoftheruntime
environmentthantheywerebefore,whichaffectsperformance.Network
roundtripscanbearealbottleneck.
N Thereisincreasinglymoredatatoprocess.Becausethepriceforfast
secondarystorageisdroppingallthetime,thereislessreasontodeleteold
data.Instead,itiskeptforfutureanalysis,amongotherusages.
Thereisdefinitelyadifferencebetweenrealandperceivedperformance.Often,
perceivedperformanceisthemoreimportantofthetwo.However,improvements
inperceivedperformanceoftendegradetherealperformance.Atypicalexampleis
usingamessagequeue.Assoonasusershavehadtheirrequestwrittentothe
queue,theythinktheworkisdoneandthattheycancontinueontosomething
else.Actually,therealperformancehasbeendegradedslightlybecausethe
completeoperationwilltakemoretimebecausequeuinganddequeuingaddsome
overhead.
Agoodmetaphortokeepinmindwhenspeakingaboutperformanceisthata
chainisonlyasstrongasitsweakestlink.Whenyoutrytooptimizeperformance,
youaretryingtotrackdownthebottlenecks-orweakestlinksinthechain-and
removethem.Inthisrespect,it'simportanttostartdesigningwithperformancein
mind.Awell-designedsystemcantypicallygetgoodenoughperformance
automaticallyorwithminorfine-tuning.Abadlydesignedsystemwillprobably
needarewrite.Todetermineifyouhavereachedthegoalforperformance,you
mustdostresstesting.Youmustaskyourself,"Whenandwherewillthechain
break?
The Importance of Test Results
Asyouknow,it'sextremelyimportanttousethetargetsystem(orone
thatisidentical)whenyoudoperformance(andscalability)testing.
Becauseofthis,somepeoplethinkthatitisuselesstoreadtestresults
forothersystemsorconfigurations.Idon'tagree.Publishedtestresults
provideimportantinformationaboutavarietyoftopics,andtherelative
differencesbetweensolutionsareoftenquitesimilarfordifferent
configurations.Consequently,Ihavedonealotoftestswhenwritingthis
book.However,becarefulaboutdrawinganyconclusionsfromthetest
resultsIgive;togetmoreaccurateresults,executethetestsinyourown
environment.Thetestresultsandallcodecanbefoundatthebook'sWeb
siteatwww.samspublishing.com.(Microsoftdoesn'tallowpublicationof
testresultsfrombetaversionsandIthinkthatmakessense.Therefore,
allpublishedresultswillbefromversion1of.NET.)
Youwillfindthatinmysolutionsproposalsthroughoutthisbook,Ifocusaloton
performanceandscalability.Oneexampleofthisismyfondnessforstored
proceduresandforrelyingheavilyonSQLServer.LettingSQLServercomplete
tasksforwhichitissuitableisanexcellentwayofgettinggoodperformanceand
highscalability,whichbringsustothenexttopiconouragenda,namely,
scalability.
Scalability
Engineerslovedefinitions.Myfavoritedefinitionofscalabilityis"Howwella
solutiontoaproblemwillworkwhenthesizeoftheproblemincreases.
2
No,as
we'veallheardbefore,performanceandscalabilityaren'tsynonymous.Asan
example,takeafairlycommonquestionintheCOM+newsgroups,"Whyisn'tan
applicationgivingshorterresponsetimeswhenpartofitisportedtoCOM+
ComponentServices?ThesimpleansweristhatCOM+/.NETComponentServices
don'treallytrytoincreaseperformance,onlyscalability.
WhatdoImeanbythis?Assumethatthereisauserrunningtheapplication..NET
ComponentServiceswillnothelphimorherbysplittinghisorherrequestsinto
severalpartsthatcanexecuteinparalleltoachieveashorterresponsetimefor
thetotalrequest-noteveniftherearemultipleCPUsofwhichseveralhavefree
capacity.Ontheotherhand,iftherearehundredsofusers,.NETComponent
Serviceswilldosometrickstotrytoservealltheuserrequestswithresponse
timesforeachrequestasclosetothesingleuserscenarioaspossible.Thus,.NET
ComponentServicesfocusestotallyonhelpingapplicationsgetgoodscalability;
performanceisnotitsdesigngoal.
Anexampleofhowitiseasytointermixperformanceandscalabilityisillustrated
inthefollowingstory.Acoupleofmonthsago,Iwasinvitedtoabrainstorming
activityatacompanywhereItriedtocomeupwithideasonhowtoimprovethe
throughputforasingleclientscenario.Theclientwasabatchprogramthat
executedausecaseoverandoveragain,asquicklyaspossible.Thiswasactually
moreofaperformanceproblem,butIalsogaveseveralideasthatwouldonlyhelp
inthescalabilitysense,suchaswhenmoresimultaneoususerswerepresent.A
typicalexampleofthiswasmetryingtoshortenthelengthofthetransactions
relativetothecompleteusecase.Itdidn'thaveanyeffectatall.IguessIgot
stuckinscalabilityideas,becausethisisusuallytheproblemathand.(Still,the
clientwillbeabletousethoseideaswhenit'stimeforthenextphase,namely
havingseveralrealuserstogetherwiththebatchprogram.)
Sometimes,performanceandscalabilitydogohandinhand.Ifanactionhasgood
performance,itwillreleasetheusedresourcesfastand,inthatway,someoneelse
canusetheresourcesquickly.Goodperformancealsooftenleadstogood
scalability.
However,sometimesperformanceandscalabilityareinoppositiontoeachother.
Anexampleofthisisusingclient-basedcachinginASP.NET.Ifyousavealotof
dataintheSession objectforeachuser,itcouldleadtoverygoodresponsetimes
foreachuser(dependingonwheretheSession objectlives).Ontheotherhand,
thatdatawillconsumealotofmemoryforeachuserandtherebylimitthenumber
ofsimultaneoususersthatcanbeservicedwithacertainamountofhardware.
Note
AsImentionedearlier,althoughreadingofficialtestresultsisimportant,
youshouldalwaysbewaryofthem-evenresultsthatarecheckedas
carefullyastheTransactionProcessingPerformanceCouncil(TPC)ones.
WhenitcomestojustTPC-C,itfavors"shared-nothingclustering(which
iswhatbothMicrosoft'sandIBM'sdatabaseproductsuse,andIwill
discuss"shared-nothingclusteringlaterinthischapter)becausethetest
harnesswillputnoloadatallonthe connectionbetweenthenodesinthe
cluster.
4
Inareal-worldshared-nothingcluster,thiswillnormallybethe
weakspot.
AsImentionedpreviously,bottlenecksarethemainenemy.Atypicalexampleof
thisisblocking,whereseveralrequestswaitforasharedresourcethatrequires
serializedaccess.Forexample,it'sextremelyimportanttodesignforshortand
smalltransactionsagainstthedatabase;otherwise,this willkillthescalability.As
youcanseeinFigure2.6,itdoesn'tmatterhowwidetheroadisformilesand
milesifacoupleofmetersarenarrow.
Figure 2.6. Bottlenecks effectively decrease throughput.
Scaling Up and Scaling Out
Developersoftentendtosolveproblemsbyenhancingthecode.Thatgoesfor
problemsinscalabilitytoo.Whenyoucheckthepricelistforhardware,yousee
thatyourcustomerwillgetalotofmemoryorCPUforthesamemoneyasifheor
shebuysoneweekofprogrammingfromanyconsultancyshop.Ofcourse,notall
problemsshouldbesolvedbyaddinghardware,butquiteoftenit'sacheapand
safesolution.
Note
Don'ttrytoaddhardwareifyourcustomerhasdecenthardwareandyour
applicationonlyscalestofiveusersorsowhentherequirementis
hundredsofusers.Thereisprobablyadesignproblemortwothatyou
shouldsolveinstead.
Microsoftoftenusestheterms"scaleupand"scaleoutwhendiscussing
scalability.(AsIunderstandit,Sunusestheterms"scalingverticallyand"scaling
horizontallyinstead.)WhatMicrosoftmeanswhentheyrefertoscalingupisto
addhardwaretoyourcurrentbox- perhapsanotherCPU,morememory,another
networkcard,afastercontroller,andsoon.Thisisasimpleandefficient
techniquetouse,because,forexample,theadministrationwillbenomore
difficult.Still,thereisalimittohowfarasinglemachinecanbeextended,and
soonerorlatertherewillbenosinglemachinethatcancopewiththeloadyou
wanttoexecute.Thenit'stimeforscalingout.
Scalingoutisaboutaddingmoremachinesandhavingthemworktogether.The
generalconceptisthatseveralsmallmachinescouldscalebetterthanasingle
largeone.Processesrunningondifferentmachineswillnotcompete,forexample,
forthesamebusandthesamememorybecauseeachmachinehasabusand
memoryofitsown.Aslongastherequestcanbehandledtotallybythemachine
thatreceivestherequest,thescalabilitywillreachhigherlevelswithscalingout.
However,thereareseveralproblemswithscalingout,onebeingadministrativein
nature.It'ssimplymuchhardertotakecareofseveralmachinesinsteadofa
singleone.AretheyallrunningthesameversionoftheDLL,forexample?In
addition,allthetoolsneededforprovidingasinglesystemimagearen'tcurrently
available.Still,thislooksverypromisingforthefuture,butyourapplicationwon't
besuitableforscalingoutautomatically.Youhavetoprepareforit.I'llcontinue
thisdiscussionlaterinthischapter.
The Database and Performance
Earlier,whenIdiscussedvariousperformanceissues,ImentionedSQLServer's
roleingettinggoodperformanceandscalability.I'dliketodiscussthisabitmore.
AsVB-programmers,wewillfinallygetastrongenvironmentandplatformwith
VisualBasic.NET,andwecanatlastuseallthegood-to-haveobjectorientation
features.Still,database-close tasksmostoftencan'tbedonefasterandmore
efficientlythanbythedatabase.
Note
Database-closetasksaretasksthatworkagreatdealwiththedatastored
inthedatabase.Forexample,suchataskcouldbetojointwotablesorto
searchforaspecificcustomer.
WhenIworkedatauniversity,Iwasresponsibleforthecoursesindatabase
design.Iwasquitealoneinthatfieldinmydepartment.Mostofmycolleagues
weremoreorthodox,object-orientedguys.Dayafterday,Iheardthatthe
databasewasjustaplacewherethestateshouldbespooledoutto,justastoa
filesystem.Thedatabasedesignwasn'timportant.Perhapstheywerejustteasing
me,butIdon'tthinkso.Itwasmoreofastrong,underlyingopinion.Myopinion
alsoisstrong,andI stillbelievethatI'mright-atleastforthetypeofapplications
thatIbuild,namelyinformationsystemstakingcareoflargeamountsofdata.The
oppositeofinformationsystemswouldprobablybeembeddedtechnicalcontrol
systems,suchasprogramming robotsordigitalphoneswitches.
So,don'tbefooled.Ifyouhavebusinessrulesthatneedtobeevaluated,andif
therulesrequiresomethingtobereadfromthedatabase,itwillbemoreefficient
tohavetherulesinthestoredprocedurethatwilltakecareoftheUPDATE itself
insteadofina.NET-componentlivingonanothertier.
Tobalancemyfavoritismofstoredproceduresalittle,I'dliketopointoutthatthe
databasetierisoftentheonethatishardesttoscaleout.Thatcouldbeone
reasonfornotputtingalotoflogicinstoredprocedures.Thereasonforthe
scalingoutproblemsisthatthedatabasewillhavetoholdstate.Thisisthenature
ofthedatabase.Therefore,purecloningofthedatabaseisn'tassimpleasofWeb
andapplication servers.
Note
IwilldiscussscalingoutthedatabaseinmoredepthinChapter5,
"Architecture.
Using the Database
Watchout.WhatIjustsaidisn'tthatit'smorescalabletomovetherequestsfor
readingdatafromthestoredprocedurestoanothertier.Ifthedatastillhastobe
read,allyouhaveachievedasaresultisworseperformanceandlowerscalability.
Youarenotsavingresourcesinthedatabasetier.Asanexample,assumethat
youhaveasimplebusinessrulesayingthatyoucan'tletthecustomeradd
anotherorderifheorshehasthestatusof"nottrusted.Inthestoredprocedure
foraddingacustomer,youhaveasimplechecktoseewhetherheorsheis
trusted,whichmightlooklikethecodeinListing2.3.
Listing 2.3 Simple Business Rule Evaluated in Stored Procedure
SELECT @aTrustedCustomer = c.trusted
FROM customer c
WHERE c.id = @customerId
IF @aTrustedCustomer = 1 BEGIN
INSERT INTO ordermaster
(customer_id, orderdate, ...)
VALUES
(@customerId, GETDATE(), ...)
END
ELSE BEGIN
--Report the error...
...
END
YoucouldrewritethecodeinListing2.3inVisual Basic.NETsothatyoufirstcalla
storedprocedurethatwilltellyouwhetherthecustomeristrusted.Ifso,youcall
anotherstoredprocedurethatinsertstheorder.Youmayevendecidetoskip
storedproceduresandcompletethistaskwithdirectSQLfromyourVisualBasic
.NETcodeinstead.Allthatyoubuyyourselfinscalabilitytermsisaworseresult.
Andkeepinmindthatthisisanextremelysimpleexample.Finally,sometimesI
getthefeelingwhenIreadbooksandarticlesthateverybodyisbuilding
applicationsforlargecorporationssuchasVISAandthelike.Ofcourse,mostof
theprocessingbeingdoneisn'tofthatscale.Evenso,it'simportanttothinkabout
scalabilityfromdayoneifyouarereachingforahigherloadthaninitially
estimated.
Other "-abilities" (Such as Maintainability, Reliability, Reusability,
Testability, Debuggability, and Interoperability)
Inthesoftwareengineeringfield,thereisalotofdiscussionabout"-abilities.
Othercollectivenamesforthemare"quality attributesand"nonfunctional
requirements.Unfortunately,theyareoftenhardtomeasure,but,evenso,they
areextremelyimportanttoconsider.
Inaddition,it'softendifficulttogetcustomerstodiscussthenonfunctional
requirements.Ifyouaskthemifyoushouldaddsupportfor"debuggability,for
example,theyoftenturnawayfromtheideaassoonastheyunderstandthatit
willcostthemintheshortrun.Theywilltellyoutonotcreatebugsinstead.Toa
largeextent,the"-abilitiesaresomethingyouhavetothinklongandhardabout
foryourself,tobeasprofessionalaspossible.
Note
Ifyouareinterestedinthesetopics,thereareplentyofbooksforfurther
reading.TwoexamplesareFrankBuschmann,RegineMeunier,Hans
Rohnert,PeterSommerlad,andMichaelStal'sA System of Patterns:
Pattern-Oriented Software Architecture
5
andG.GordonSchulmeyerand
JamesI.McManus'sHandbook of Software Quality Assurance.
6
Maintainability
Isitpossibletochangethesystemandextendittomeetnewrequirements
withouterodingtheoriginalarchitecture?Forthistohappen,theinitial
architecturemustbeflexibleenoughtoadapttounknownchangesinthefuture.
Compareittoabuilding.Ifyoudon'tlikethebasicarchitectureandyoudecideto
moveawall,thereisalottoconsiderifthealterationwasn'tpreparedforup-
front.Theresultmightbethatthewholebuildingcollapses.Moreoftenthannot,
problemsdon'tarisethatquickly.Asnewfunctionalityisadded,someofthe
originallyplanneddetailswillnotbeusedanymore,andotherswillbeused
differentlythan whatwasplanned.Afterawhile,youcan'trecognizetheoriginal
architecture.
Note
SomeofyoumightfeelthatExtreme Programming
7
saystheoppositeto
whatIjustsaid.Idon'tagree.Extreme Programming statesthe
importanceofhavingthechancetomakechanges.Don'tplanforall
possiblechanges,butbepreparedforthem.
It'salsoimportantthatthearchitectureisn'tsoflexiblethatitisimpossibleto
understand.Soonerratherthanlater,thechangeswillthenconflictwiththe
architecturalvision.Ofcourse,documentationisalsoveryimportantto
communicatethearchitecturesodeveloperswon'tunintentionallydestroyit.Test
driversandassertionsthatI discussindepthinChapter3,"Testing,willalsoadd
qualitypartstothedocumentation.
Anotherveryimportantaspectofmaintainabilityistoconsiderwhetheritis
dangeroustomakeachangesomewhereduetoalargeriskofsideeffects
somewhereelse.Ahighdegreeofencapsulationwilltakeyoualongway,because
therewillbefewersideeffectsifyoumakechangestoanencapsulated
implementationcomparedtoonethat,forexample,usesalotofglobalvariables.
Strongtypingwillalsohelptolessenanydangerinmakingachangebecausethe
compilerwilltellyouifyou,forexample,trytomakeanimplicitcast.Thiscanbe
areasonforthinkingmoreabouttypedDataSets,forexample.InFigure2.7,you
canseeanillustrationofa"systemthatisn'tmaintainableatall.Takeoutacard
andthecardhousewillcrash.
Figure 2.7. A house of cards is an obvious example of something with low
maintainability.
Yetanotheraspectofmaintainabilitycanbefromtheadministrator'spointofview.
Isiteasytoswitchconnectionstrings?Aretheresemaphores(flags)thatwillon
occasionstay"upandrequireanadministratortotakethemdowneverynowand
then?Isitpossibletoexaminecustomperformancecountersandsuch?
Note
InChapter9,"ErrorHandlingandConcurrencyControl,Iwillpresenta
solutionthatdemonstratesanadministrator-friendlytechniquefor
providing pessimisticlockinginadisconnectedscenario.
Reliability
Reliabilityisoftendiscussedintermsoffaulttoleranceandrobustness,butitis
alsolookedatintermsofrecoverability.Hardwarecanprovideyouwithsome
faulttolerance,butinthisbook,Iwillonlydiscusssoftwareaspectsofreliability.
Inanefforttoavoidconfusionduetothemyriadtermsthatareusedinthe
industry,I'lldiscussreliabilityintermsof
N Usingpreventativemeasures
N Activelytakingcareofaproblem
N Usingrecovery/failoverwhenaproblemoccurs
Using Preventative Measures
AsI'msureyouknow,insteadoflettingaproblemoccurbeforetakingcareofit,
youcantakeprecautionstomakeitlessprobablethataproblemwilloccuratall.
Forexample,mostofuswouldratherchangetheoilinourcar'sengineeverynow
andtheninsteadofreplacingtheenginewhenitcrashesbecauseofbadoilorlack
ofoil.Atypicalexampleofapreventativemeasureinatechnicalsenseistouse
thenewfeatureinCOM+1.5torequestrecyclingofyourserverprocesses,say,
onceaday.
Actively Taking Care of a Problem
Youcanalsothinkofgooderrorhandlingwithautomaticretrymechanismsasa
wayofachievingfaulttolerance.It'snousethattheuserhastoclickaRetry
buttontomakeanewtrybecausetherewasalockingconflict;theprogram
shouldinsteadretryseveraltimesonitsown.Anotherproposalistousemessage
queuingoften.Ifthemessagehasbeenwrittentothequeue,manyretriescan
occur.
Anotherwaytosee itisthatthesystemwillbeabletocope,forexample,with
incorrectinput,andwilljusttelltheclientthattheinputwasn'tacceptable,
withoutgoingtoanunexpectedstate.
Note
IwilladdresssuchreliabilityaspectsinChapter3,whereIdiscuss
assertionsanddesignbycontract.
Using Recovery/Fail Over When a Problem Occurs
.NETComponentServiceswillhelpagreatdealwhenproblemsoccur.For
example,ifthereisaseriousexception,theprocesswillbenaileddownanda
freshprocesswillautomaticallybestarted.Meanwhile,onewaytogetsafe
recoverabilityistouseatransactionlog,asSQLServer2000does.Anotherwayis
toexpectfailovertodothejob;thatis,whenonemachinefaults,another
machinetakesover.AtypicalproductthathelpswithfailoversupportisMicrosoft
ClusterServicesinWindows2000.
Note
Productsneedtobecluster-awaretobenefitfromMicrosoftCluster
Services.OneexampleofsuchaproductisSQLServer2000.
Let'sendthissectionwithyetanotherexample.Usinglazyinitializationisless
efficientthanmakingtheinitializationonceatstartup.(Anexampleofinitialization
couldbetoreadtheconnectionstringtousefromaconfigurationfile.)Onthe
otherhand,becausethereisacheckbeforeeachusagethattheinitializationhas
beendoneandthattheinstanceisokay,youcaneasilymake arefreshactionifa
problemisfound.
Reusability
Fromthebeginningoftheobject-orientationhype,reusabilityhasbeenits
nirvana.Sofar,thepromiseshaven'tliveduptoexpectations.Thereareprobably
severalreasonsforthis.Forexample,somemightarguethatcomponentsarea
betterreuseunitthanclassesincode.Inanycase,reuserequiresalotofwork
andwon'thappenautomatically.
Anotherwaytothinkaboutreusabilityistonotcreatereusablecomponentsin
advance,buttotryreusewhenyoufacetheproblemagain.
7
Nevertheless,you
shouldn'tcreatebarriers,andIwillbeevaluatingtheproposalsinlightofthisin
thecomingchapters.
It'simportanttounderstandthatthereareseveralgranularitiesforwherereuseis
possible.Youcanreusecomponentsorcompleteassemblies,storedprocedures,
user-definedinterfaces,orwholelayers.Thelocationofyourbusinessrulesisa
majorfactortoconsiderwhenyouthinkaboutreuse.Take,forexample,the
portionofanarchitectureasshowninFigure2.8.Thereyoucanseethatthe
businessrulesarelivinginalayer"beforethestoredprocedures.
Figure 2.8. Example of an architecture where the stored procedures can't be reused
without also reusing the components in the business logic layer.
Note
Anassemblyin.NETisapackageofclasses,andthereistypicallyaone-
to-onemappingtoaDLL.Anassemblycontainsoneormorenamespaces,
andassembliesarealsothesmallestversionableunits.
ForadesignasshowninFigure2.8,reuseofstoredproceduresthattakescareof
updatingisimpossible (oratleastunsuitable)becausethebusinessrulesare
locatedinthebusinesslogiclayer.Theonlylayersthatarereusablearethe
businesslogiclayerandconsumerstothatlayer-atleastasfarasoperationsthat
runtransactionsagainstthedatabasego.(Ifyoureuseamethodthatfetches
somerows,thatdoesn'tmatter.)If,ontheotherhand,youmovethebusiness
rulestothestoredprocedures,youcanreuseeverylayer,includingthestored
procedures.
Note
IwilldiscussbusinessrulesinmoredetailinChapter7,"BusinessRules.
Inaway,reusabilityoverlapswithinteroperability,whichIwilldiscusslaterinthis
chapter.Youcan,forexample,reuseaVB6-writtencomponentfroma.NET
component.Thisisatypicalexampleofinteroperabilitybetweenunmanagedand
managedcode.(Managedcodeisexecutedandmanagedbythecommon
languageruntime.Unmanagedcodeistheopposite;atypicalexampleof
unmanagedcodeisCOMcomponentswritteninVB6.)Inaddition,youcan
definitelydesigntogetmorereusablecomponents.Aclassicproblemishowand
wheretocontroltransactions,whichcanaffectthepossibilityofeasilyachievable
reuse.
Note
IwilldiscusstransactionsinthefaceofreusabilityinChapter6,
"Transactions.
Don'tforgetthatit'snotonlytheimplementationsthatcanbereused.Reusing
designelementscanbearealtime-savertoo,andcanalsohelpincreasequality.
Oneexampleofthisiswell-designed,user-definedinterfaces.Anotherexampleis
designpatterns.
1,5
Testability
Somepeoplesaythatdebuggingandtestingarethesamething.Idon'tagree.
Testingiswhatyoudotoseeifyoucanfinderrorsintheapplication.Debuggingis
whatyoudo whenyoutrytolocateaknownerrorandcorrectit.Still,some
techniquesworkforbothtestinganddebugging.Oneexampleofthisisusing
assertions,whichtellyouthatyouhaveanerror.Theprogramistestingitself,ina
way.Atdebuggingtime,assertionswillalsobeasignaltoyouaboutwhereyou
shouldstartlookingfortheproblem.
Ifyoucreatealooselycoupledsystem,thatis,whenthedifferentpartsaren't
tightlydependentoneachother,butcaninteroperatewithotherpartsinsteadand
canbeusedinisolationwithoutallthepartsitisnormallyinteroperatingwith,you
willalsoincreasetestability,becauseeachcomponentcanbemoreeasilytestedin
isolation.Thesamegoesifyouseetoitthatyouwritestubsthatreturn
reasonable valuesearlyon,eventhoughtherealimplementationhasn'tstarted
yet.(Astubinthiscontextisanemptymethod,butwiththecorrectsignature.
Thestubcanalsodeliverdummyvalues,tosimulatetherealmethod.)
Componentsdependingonthosestubscanbetestedearlyon.
Cachingmaydecreasetestability,becauseraceconditionsmightgiveintermittent
problemsthatmayonlyshowthemselvesonceuntiltheserverisshutdownand
restarted.Thismeansthatcachingwilldecreasetheexposureoftherisks,which
makesithardertofindproblems.Youcanrestassuredthatthebugwillshow
itselfinanimportantdemooratproductiontime!
Note
Anexampleofaraceconditioncanbetwothreadsthatbothwantto
initializeavalue.Thefirstthreadcheckstoseeifthevaluehasbeen
initializedandfindsoutthatithasn't,soitstartstogatherinformationso
itcansetthevalue.Meanwhile,thesecondthreadhasalsoseenthatthe
valuehasn'tbeeninitialized;so,itstartstogatherinformationtoo.
Note
IwilldiscusstestinginmoredetailinChapter3.
Debuggability
Iaskedafriendwhoisa researcherinsoftwarearchitectureiftheytalkabout
"debuggabilitytoo,and,althoughheunderstoodwhatImeantimmediately,he
hadneverheardoftheterm.PerhapsIhaveinventedtheword.
Thereareseveralthingsyoushoulddotoprepareyourapplicationforeasyand
efficientdebugging,butthesinglemostimportanttechnique,inmyopinion,isto
addtracingsupportsothatyourapplicationcantellyouwhatisgoingon.For
example,sayadistantcustomercallsyouandsayshehasaproblemwith the
applicationyoubuilt.Becauseitseemstobeastrangeproblem,theonlyanswer
youcangivehimisthatyouwillhavetoaddtracingtotheapplicationandsend
himanewversioninacoupleofdays.Itwouldbemuchbetterforyou(andyour
customer)ifyoucouldjusttellhimto"turnaswitch(forexample,byaddinga
rowtoa.config file)toturntracecallson,runthescenario,andsendyouthe
tracelog.Youhaven'tsolvedtheproblemyet,butyouhavegottenofftoavery
quickstart.
Theinteractivedebuggersthatwearenowaccustomedtoshould,ofcourse,be
usedasmuchaspossible,buttheyhaveshortcomingsthattracinghelpstosolve.
Examplesofshortcomingsthattracingsolvesincludeeasierdebuggingof
multiuserscenariosanddebuggingonlineand/oroffsitewithoutinterfering(too
much)withthesystem.Withatracingtool,youwillalsogetabetteroverview
whenyoustartdebuggingthanwithadebugger,whereyou'reindetailmode
directly.
Anotherwaytoincreasetheproductivityofdebuggingistohavetheapplication
logasmuchinformationaspossiblewhenthereisanunexpectedsituation.That
way,youcanhopefullyreproducethesituationwhenyouinvestigatetheproblem.
Note
Chapter4,"AddingDebuggingSupport,isdedicatedtoincreasing
debuggability.
Interoperability
Althoughinteroperabilitycanmeanseveralthings,Iwilluseitinthesenseof.NET
componentscoexistingwithCOM(andCOM+)componentscreatedinVB6and
withpurchasedcomponents.Indoingso,IassumethatmostofuswilluseVB6
foratleastanotherfewyears.Itjustdoesn'tmakesensetorewritealltheold
componentsina.NETlanguagedirectlywhentherearesomanyotherprojects
thatmustbedevelopedforthefirsttime.Microsoftisawareofthisandhas
investedalotoftimeandmoneyincreatingstronginteroperabilityin.NETwith
existingCOMcomponents.ThewayIseeit, thisiscrucialforthewideacceptance
andsuccessof.NET.
Thecoexistenceandinteroperabilitybetween.NETandCOMcomponentsisreally
twofold.It'simportantthat.NETcomponentsareabletouseCOMcomponents
andviceversa.Althoughitispossible,justhowgoodwilltheinteroperabilityin
certainsituationsbe?AndhowwilltheneedforcoexistencewithCOMcomponents
affectyourdesigndecisions?ThesearequestionsIwilltrytoanswerinthe
evaluationsofthevarioussolutionproposalsthroughoutthisbook.Iwillalsofocus
onhowtolet.NETcomponentsinteroperatewithyourstoredprocedures.Inmy
opinion,therehasbeentoolittlesaidaboutthisovertheyears.
Thecommonlanguageruntimealsohasalottodowithinteroperability.For
example,thegarbagecollectorwillhelpmakeinteroperabilityeasiertoachieve.
UsingthecommontypesystemandtheBaseClassesLibrary(BCL)aretwoother
waystoaffectinteroperability.Inaddition,thereareinteroperabilityaspects(few,
butsome)tothinkaboutevenbetweenlanguages,suchasVisualBasic.NETand
C#.Forexample,ifyoucodeinC#,rememberthatVisualBasic.NETisnotcase
sensitive.
Yetanotheraspectofinteroperability,namelycoexistencewiththird-party
components,isofcoursehardtotargetinspecificterms,butIwillgivesome
generalcommentshereandtherethroughoutthebook.Oneexamplethatcan
helptremendouslyisthepossibilityofdeclarativetransactions,whichisoneofthe
.NETcomponentservices.
OneruleItrytoadheretoisnottousedistributedtransactions(whichiswhatyou
getifyouusedeclarativetransactions)ifthereisonlyoneResourceManager
(RM),suchasSQLServer.Inthiscase,Iuselocaltransactionsinstead,soasnot
towasteresources andtogetbetterscalability.Oneexceptiontothisruleof
thumbiswhenyouhavetodealwithacomponentwheretheimplementationis
unknowntoyouandwherethereisnowayofaddingthecomponent'sbehavior
intoyourlocaltransactions.Then,distributedtransactionscometotherescue.
Note
Iwilldevotealotoftimetodiscussingtransactionsandtipsonhowto
optimizeforeachcaseinChapter6,"Transactions.
Don'tforgetthatinteroperabilityaspectshaveagreatinfluenceonwritingserviced
components,becausetheserviceswillstillbeinunmanagedcodeandyour
componentswillbeinmanagedcode.Becauseofthis,youcan'texpectstatic
methodsasrootmethodstobeserviced,forexample.
Productivity
Computersaregettingfasterallthetime,butourproductivityisn'tgrowingat the
samepace.Meanwhile,theproductivityfactormightbeinconflictwith,say,the
performancefactor.Anexampleofthisisthatyouneedtoknowthenamesof
yourmethodsduringexecutiontime.Thisisusedfortracing,forexample,which
I'lldiscussingreaterdetailinChapter4.Itwouldbeveryfasttohard-codethe
nameofthemethodasaconstantandhandthatoverineverytracecallinthe
method,butwhenitcomestoproductivity,itwouldbelesseffectivethanusing
reflectiontogettothemetadata.Youwillfindanexampleofhowthatcanbe
doneinthecodesnippetinListing2.4.
Listing 2.4 Finding the Name of the Calling Method
Dim st As New StackTrace(1)
Dim sf As StackFrame = st.GetFrame(0)
Dim aBuffer As String = sf.GetMethod.Name.ToString()
It'sverycommonthatdeveloperscopyandpastecode(commonlyreferredtoas
editororclipboardinheritance)and,withthemetadataapproach,youwon'tget
anyextracopy-and-pasteproblems.(Asyouknow,thereareothermoreserious
problemswithcopyandpaste,butthat'sanotherstory.)
Anotherexampleofproductivitybeinginconflictwithperformanceisthatyou
don'thavethechanceofcreatingglobalconstantswhenprogramminginT-SQL.
Theclosestsolutionisprobablytohaveatablewithalltheconstantsandthen
encapsulatethetablewiththehelpofaUser-DefinedFunction(UDF)foreach
constant.Thisgivesveryreadableandmaintainablecode,butthisexample,too,
sacrificesperformance.
Youdon'twanttomakeachoice?Well,onewaytohavethebestofbothworlds
couldbetokeepthesourcecodeintactwiththeproductivesolution.Whenitis
timeforfinaloptimization,youcopythesourcecodeandexecuteasimplesearch-
and-replacetoolonitthatexchangessomecodesnippetsforhard-codedsnippets.
Forthepreviousexamples,itwouldprobablytakeyoulessthananhourtowrite
suchatool.
Anothergreatway ofincreasingproductivitywithoutsacrificingperformanceisto
usegeneratorsandtemplates.Then,writingthecodeisfaster.Ofcourse,running
itwillbeasfastasifithasbeenwrittenbyhand.
Howcanproductivitysufferbecauseofthedesign?Atypicalscenarioisthatan
overlayeredarchitecturehasbeenchosen.Layeringcouldbegoodforproductivity
becauseseveralgroupscanmoreeasilybeworkinginparallel,but"lagomr
bst,aswesayinSweden:"Allthingsinmoderation.
Finally,twoofthemostproductivity-killingfeaturesofaprojectcanbewhen
testinganddebugginghaven'tbeenplannedforupfront.Thebadnewsisthatthis
oftenwon'tbecomeevidentuntillateintheproject.
Security
Let'sfaceit.Developersfindsecuritydifficultand"intheway.Firewallsareoften
wronglyconfiguredandcauseproblemsatdeploymenttime.Anauthentication
problemaddsanothertasktothealready-delayedprojectplan,theneedfor
certificateswasnotarequirementupfront,andsoon,and soforth.Still,security
isareality,andthewaytocopewithitistothinklongandhardaboutit.Because
ofthis,Iwillevaluateallthesolutionproposalsinthebookwithregardtosecurity,
discussingwhetheranyextrasecurityriskswillbecreatedbyagivensolution.
Let'stakeafewexamples.Couldthelocalizationofthebusinessrulesopenupa
securityproblem?Assumethatsomeofthebusinessrulesareputinthestored
procedures.Thewaytosendtheuseridentificationisasanordinaryparameterto
thestoredprocedure(becauseyoushouldn'tlettheenduserslogintothe
databasewiththeirownidentityduetoconnectionpoolingreasons,forexample).
Thisislesssecurethanifnouseridentificationhastobesenttothedatatier.
Usually,theproblemisprobablynotbigatall,butit'stherebecauseifsomebody
couldinterceptthecall,heorshecouldchangeitsothathisorherown
credentialsareusedinstead.Supposethatyouusedatacachingalot,andfor
sensitivedata,too.Isthedatacacheassecureasyourdatatier?Probablynot.
We'lldiscussmoreaboutthisandothersecurity-relatedissuesthroughoutthe
book.
Farm- and Cluster-Enabling
Ofcourse,Icouldhavediscussedhowtofarm- andcluster-enablecomponentsin
the"PhysicalRestrictionsandPossibilitiessection,butIfeelitisofsuch
importanceandsuchalongdiscussionthatit'simportanttolookatitseparately.I
starteddiscussingscalingoutearlierinthechapter.Let'scontinuewiththis
discussionabitmore.
AsIsaid,lettingseveralsmallmachinesworktogetherinanintelligentwaycan
giveextremelygoodscalability,especiallyiftheshared-nothingmodelisused.In
Figure2.9,youcanseethateachdatabaseserverhasitsowndisksystem,
memory,bus,andsoon.Thatway,thereisnobottleneckbecauseofseveral
machinescompetingforthesamebus,forexample.
Figure 2.9. A schematic picture of "shared nothing."
Therearethreebasicwaysofachieving"sharednothing:
N Cloning
N Partitioning
N Replication
Iwilldiscusseachofthese,aswellasshareddiskclusteringnext.
Cloning
Incloning,aWebserverandapplicationserverisduplicatedsothattheloadis
splitbetweentwoormoremachinesinsteadofone.Someload-balancing
mechanismisneeded,butwhenthatisinplace,cloningisofteneasilyused.The
load-balancingmechanismissimpletosetupandgivessomefaulttolerance(as
failover)automatically.Note,inFigure2.10,thattheWebandapplicationservers
areallequalanditdoesn'tmatterwhichonetheuserhits.
Figure 2.10. Cloning.
Partitioning
Anothercommonapproachistopartitiontheapplicationserveroverseveral
machinessothatsomecomponentsliveononeserverandsomeonanother.
Unfortunately,thiswillsufferfromperformanceandscalabilityproblems.Itwill
alsobemorevulnerablebecauseifoneserverisdown,itwon'tbepossibletouse
theapplicationatall.It'smostoftenpreferabletousecloninginsteadof
partitioningfortheapplicationserver,atleastwhenitcomestoperformance.On
theotherhand,therecouldbeotherreasonsforpartitioningtheapplication
server,suchaswhenyouwanttoprocesssomeofthelogicinsideofthe
DeMilitarizedZone(DMZ)andsomeofthelogicoutsideofit.Asalways,it'sa
tradeoff.InFigure2.11,youcanseeanexampleofpartitioningwherethe
applicationserverhasbeenpartitionedbetweenbusinesslogicanddataaccess.
Figure 2.11. Partitioning example 1. In this example, the application server is partitioned
in two.
The DeMilitarized Zone (DMZ) in Software
Architecture
TheideaoftheDMZistocreateasemisecurezonebetweenthefirewall
andtheInternetandthefirewallandthecorporateintranet.Forexample,
ifyouwanttoputasensitiveInternetWebapplicationintheDMZ,you
candictatethattheonlytimethattrafficgoesthroughthesecondfirewall
tothecorporateintranetiswhentheWebserverhastocontactthe
databaseserverforaccessingdata.
EventhoughIthinkcloningisaverygoodstrategyforscalingoutWeband
applicationservers,itdoesn'tworkwellautomaticallyforthedatabaseserver.The
reasonforthisisamatterofstate.BecausethecomponentsrunningontheWeb
server/applicationservercouldbedesignedtobestateless,itdoesn'tmatter
whichservertheuserwillhitthenexttime.Therearen'tanydifferencesasfaras
the userisconcerned.Whenitcomestothedatabase,it'sanentirelydifferent
story.Thedatabaseisinherentlystateful,sotheusermusthitthecorrect
databaseservereachtimetofindthestateheorsheneeds.Ifalldatabase
serverslookalike,sothateachofthemhasthesamestate,therewillbeanother
problem,namelywiththeUPDATEsthatmustbepropagated.
Instead,thedatabasecanbepartitionedoverseveralserverssothatthepartof
thedatabasethattakescareoforderscanoccupyoneserver,theshipmentscan
occupyanotherserver,andsoon.Thenyouhaveamajordesigninfluence.A
morerecentpossibilityfoundinSQLServer2000isDistributedPartitionedViews
(DPVs)thatletyousplitatable,suchasordermaster,overseveralmachines.The
partitioningistransparenttothelayerclosesttothedatabase.Itcanalsobebuilt
byhand,butthenthelayerclosesttothedatabasemustknowhowthe
partitioninghasbeencompleted.Eitherway,it'sextremelyimportanttofinda
distributionkeythatsplitsthedata,orrathertheload,evenly.Youcanseethat
thecustomer tablehasbeensplitoverthreedatabaseservers-East,Central,and
West-inFigure2.12.
Figure 2.12. Partitioning example 2. In this example, the database server has been
partitioned.
Replication
Inseveralpartitioningscenarios,therewillbeaneedforduplicationoflookup
tables(andduplicationoftherowsinthosetables)becausetherewillbe
referentialintegrityrelationshipsthatneedtobechecked.Whenthoselookup
tablesaretobeupdated,atwo-phasecommittransactionisneededif"real-time
consistencyisarequirement.Replicationcangivea"real-enoughtime
consistencysolutiontotheproblem.
OneproblemwithDPVsisthatallqueriescan'talwaysbeansweredbyone
machine.Instead,severalmachineshavetocooperatetoproducetheresult,
whichdecreasesperformance.Amorehigh-performancesolutionwouldbeto
clonethedatabasesinsteadsothatalldatawillbefoundateveryserver.The
readingwillbeveryfastandthesolutionwillbeveryscalable-thatis,untilyou
takeUPDATEsinconsideration.Ifyouneedreal-timeconsistencybetweenthe
databases,youneedtousetwo-phasecommittransactions(distributed
transactions),whicharenotonlyverycostlybutalsovulnerable.Ifoneserveris
downatthetimeofthetransaction,thetransactioncan'tbefulfilled.Figure2.13
showsaschematicdescriptionofreplication.Youcanseethattheworkstationis
onlytalkingtooneofthedatabaseservers,butthechangeswillbereplicatedto
theseconddatabaseserver.
Figure 2.13. Replication.
Shared Disk Clustering
Sofar,I'vediscussed"sharednothingclustering.Anotherclusteringmodelisthe
shareddisktechnique.Onewaytousetheshareddisktechniqueistohaveadisk
systemwiththedatabasethatissharedbyseveralserversactingasdatabase
servers.Figure2.14providesanexampleofhowshareddiskclusteringmight
look.
Figure 2.14. Shared disk clustering.
AsMicrosoftseesit,themainreasonforshareddiskclusteringnowadaysistoget
faulttolerancebyusingfail-overtechniques.Withshareddiskclustering,youdon't
havetothinksomuchaboutfaulttolerancewhenyoudesignyourcomponentsas
youdowithshared-nothingscenarios.
Itwillprobablybequitecommontocombineshareddiskwithshared-nothingso
thateachdatabasenodewilluseashareddiskcluster.Figure2.15showssuchan
example.Ingeneral,shared-nothingisusedforscalabilityandshareddiskisused
forreliability.
Figure 2.15. Combination of shared disk and shared-nothing.
Caching and Clustering
Atthesametimeyoustartwithfarmsandclustering,youmustbecareful
withyourcachingsolutions.AssumethatyoucachedataataclonedWeb
serverandthatpieceofdataischanged.Howcanyoumakethechange
tohitthecacheatalltheWebserversatthesametime?Doyouhaveto?
Ifyoudo,it'sprobablybetternottousecaching.
References
1.E.Gamma,R.Helm,R.Johnson,J.Vlissides.Design Patterns: Elements of
Reusable Object-Oriented Software.Addison-Wesley;1995.
2.http://wombat.doc.ic.ac.uk/foldoc
3.http://www.tpc.org
4.G.F.Pfister.In Search of Clusters, Second Edition.PrenticeHallPTR;1998.
5.F.Buschmann, R.Meunier,H.Rohnert,P.Sommerlad,M.Stal.A System of
Patterns: Pattern-Oriented Software Architecture.Wiley;1996.
6.G.Schulmeyer,J.McManus.Handbook of Software Quality Assurance, Third
Edition.PrenticeHall;1999.
7.K.Beck.Extreme Programming Explained: Embrace Change.Addison-Wesley;
1999.
Chapter 3. Testing
IN THIS CHAPTER
N AShortIntroductiontoTesting
N SupportforAutomaticTestingwithaStandardizedTestBed
N Assertions
N TheDiagnose/MonitorTool
N MiscellaneousTips
N EvaluationofProposals
Although mostsystemarchitecturebooksincludeachapterontestingtowardthe
endofthebook,Ibelievethattestingshouldbeconsideredinthebeginningof
everyprojectandateverystepalongtheway.Therefore,Iamincludingachapter
dedicatedtotesting earlyoninthisbook,inthehopethatyouwillplananddesign
yourprojectswithtestinginmind.
Often,inreal-worldprojects,testingisn'tusedasmuchasitshouldbeand,when
itisused,itisconsideredextremelytime-consuming.Meanwhile,developersoften
claimtheyneedathird-partytestingtooltogetstartedontheirtestingwork.
However,testingismoreaboutorganizationalattitudethanaboutfancytools.If
youvaluetestinginthebeginningofaproject,itwillhelpyouinthelongrun,
regardlessofthetoolyouuse.Ifirmlybelievethattheamountoftestingyoudois
inverselyrelatedtothestresslevelinyourproject.Inaddition,youprobably
createmorebugsunderpressurethanwhenyouareworkinginacalm
environment.
Mostofthischapterwillbedevotedtomakingtestingautomatictoinexpensively
increasethelong-termqualityofyourapplication.Wewillstartbylookingatthe
varioustypesoftestingavailable.Iwillthenpresentaproposaltoautomate
testingthatyou canadapttoyourownprojects.Nextwewilllookathow
assertionscanbeusedtomakethecodetestitself.Finally,wewilldiscussa
diagnose/monitortoolaswellasimportanttestingtips.Iwillconcludethechapter
byevaluatingtheautomatictestingandassertionsproposalsIhavemadebased
onthefactorsIpresentedinChapter2,"FactorstoConsiderinChoosingaSolution
toaProblem.
A Short Introduction to Testing
ThissectionisnotmeanttobeusedasaguidelinefortheQualityAssurancefolks;
rather,itisarecapoftestingissuesfordevelopers-bothconsideringthetesting
levelandthetypeoftestingneeded-beforewestartgettingourhandsdirty.
Considering the Testing Level
Beforeyoubegintestinganyproject,youmustfirstconsiderthelevelatwhich
youwantyourtestingeffortstobeapplied.Youcouldtesttheentireapplication
(andyoudefinitelyshould)butit'simportanttoconsiderindividuallythepartsof
thewholeandtotestthemearlyon.Ifyoupostponetestinguntiltheapplicationis
done,you'reinfortrouble.Itwillbetoolatetofixseriousproblems,andyoumost
likelywon'tbeabletoconductenoughtestingduetolackoftime.Ihighly
recommendyoutestthesubsystemsastheyarebeingbuilt,buteventhatistoo
granularandtoo"latealevelifitisthefirstoneatwhichyoutest.Starttesting
directlyattheunitlevelforcomponentsandstoredprocedures,andtestallof
them.
Acoupleofyearsago,Ihadaheateddiscussionwithmywife,whowasworking
asaprojectmanagerforatestgroupatthetime.Shesaidthathergroupwasin
awaitstatebecausetheycouldn'tstarttestingtheirsubsystembecauseitwas
dependentonanothergroup'ssubsystemthathadn'tbeenbuiltyet.Itoldherthat
theyshouldcreatestubstosimulatethedelayedsubsystem,givingthethroughput
thatwastobeexpectedandsoon.(Stubsareemptymethodsbutwiththe
signatureoftherealmethods.Theyareusedtosimulatethefinishedmethods,to
makedependentpartstestableearlyintheprocess.)However,mywifethoughtit
wouldbetooexpensiveforhergrouptocreatethestubsandsowantedtowait
untiltheothergroup'ssubsystemwascomplete.Asyoumightguess,Ididn't
agreewiththis.Ithinkthatcreatingthestubswouldhavebeencheaperthan
delayingthetests.(Ontheotherhand,asItriedtoremindmyself,thelessyou
knowaboutaproblem,theeasieritistosolve.IguessIoughttosaythisshould
mywifeeverreadthisbook.)Thebottomlineis:Testattheunitlevel,andthen
atthesubsystemlevel(bytestingallthecomponentsinthatsubsystem),and
finallyatthecompletesystemlevel,withallthesubsystemsworkingtogether.
Determining the Type of Testing
Dependingonthelevelatwhichyouwilltestyoursystem,youcanandshoulduse
differenttypesoftesting.It'simportanttonotethatnosingletechniquewillbethe
onlyoneneeded.Let'stakealookatthedifferenttypesoftestingyoushould
consider.
Development Testing
Asdevelopersworkwithacomponent,theyobviouslymusttestthecomponent
fromdifferentperspectives.Doesitworkinthiscase?DoIgettheresultIexpect
forabunchofdifferentcombinationsofinput?Andsoon.
Historically,VBhasbeenagreattoolforwritingcode,testingit,andcorrecting
problemsasyoutest,continuingtowritemorecodeasyougo.Thisisavery
productiveapproach.Whatisnot aproductiveapproachistohaveonlythefinal
UserInterface(UI)asthetestdriverwhenyoubuildthecomponents.Thereare
severalproblemswiththis,includingthefollowing:
N TheUImightnotbefinishedyet.
N Clickingthroughseveraldialogsbeforeyougettowhereyoucantestyour
methodisnotjusttimeconsumingandfrustrating,itcanalsoleadtocarpal-
tunnel-syndrome-likesymptoms.
N Itwillseldombeeasytotestallthefunctionalityofyourcomponent,asalot
ofitwillbe"hiddenbytheUI.(Thisisespeciallytrueforerror-handling
code.)
Mostdevelopersconductdevelopmenttestingastheygoalong.However,the
processofdevelopmenttestingmayneedtobeformalized.We'lldiscussthisin
moredetaillaterinthechapter.
Code Reviewing
Onepowerfultestingtechniqueissimplytoreviewyourcode.Thisisespecially
efficientifyouasksomeoneelsetoreviewyourcode.Inmyfirstreal-world
project,amentorcameintocheckonusnowandthen.Onetimehesaidtome,
"Jimmy,showmeyourbestfunction.Iquicklyguessedthathewantedtosee
somecodewithalotofcomments,andIrememberedonefunctionIhadrecently
writtenthatwasheavilycommented.(Yes,allmycodewascommented,butthis
functionwasmoresothanaverage.)Iproudlyshowedhimthatsample.Afterhalf
anhourorso,Iwasn'tsoproudofthecodeanymore.Hehadgivenmefeedback
onhowIcouldimproveeverysingleaspectofthefunction.Perhapshewastoo
toughonme,giventhatitwassoearlyinmy career,butontheotherhand,it
helpedmeimprovethequalityofmycodegreatly.Notonlydidhisideasinspire
me,Iwasalsoafraidthathewouldaskmetoshowhimmybestfunctionagain!
Afeworganizationshaveformalizedcodereviewingsothateverycomponentmust
besignedbyareviewer,forexample.Ontheotherhand,themajorityof
organizationshaveonlyjustbeguntodiscussimplementingacode-reviewing
policy.Irecommendyoustartconductingcodereviewingifyouhaven'talready
doneso.Letsomebodyreadyourcodeforanhour;youwillbesurprisedbythe
amountofpotentialproblemsthatsomeoneotherthanyourselfcanseeeasily.
I'veencounteredmanyexamplesoftheimportanceofcodereviewing.Onein
particularstandsout.Acolleagueofmineoncebuiltanapplicationthatwastobe
usedonanationaltelevisionshowthatwascollectingmoneyforcharity.My
colleaguehadahardtimeconvincingthecustomerthatitwasimportanttotest
theapplication,andsohewasveryanxiousabouthisapplicationfailingduring
primetime.Therefore,heaskedmetoreviewthecode.Althoughheisthemost
skilledprogrammerIknow,andhadreviewedthecodeseveraltimeshimselfand
carriedoutstresstestsandsuch,nevertheless,intwohoursIwasabletopoint
outthreemajorrisksthathewasabletoremovebeforeusingtheapplication.The
applicationranwithnoproblemsontheshow.(Perhapsitstillwouldhavewithout
myreview,butitwasamuchsmaller"perhapswiththereview.)
Codereviewdoesinvolvesomeproblemsyouhavetowatchoutfor.Forexample,
youmusttakecarethatprogrammersdon'tfeelpersonallyinsultedbythereview
process.Also,becarefulthatthecodereviewdoesn'tfocussolelyonchecking
formalism,suchasthatthenamingconventionisfollowedandsoon.Such
reviewingisimportant,ofcourse,butitislessimportantthanfindingpartsofcode
thatmayberisky,especiallyifallthereviewtimeisspentdebatingonhowa
vaguenamingruleshouldbeapplied.
Finally,mostofwhatI'vesaidaboutcodereviewcanbevaluableandshouldbe
usedforreviewingtheproject'sdesignaswell.Asyouknow,theearlieraproblem
isfound,theeasierandcheaperitistofix.
Integration Testing
Althougheachunitshouldbetestedseparately,theymustobviouslywork
togetherwhenyouintegratethem.Therefore,it'simportanttotestthe
componentsaftertheyhavebeenintegrated.
Isuggestthatyouscheduleintegrationtestsatspecificpointsintime-perhaps
onceaweek,perhapsasanightlybuildwithautomaticintegration-by
automaticallyextractingthecodefromtheversioncontrolsystemyouuse.Ifyou
don'tschedulesuchtesting,youriskgettingintothesituationI'vebeeninseveral
times.SayI'mabouttointegratemycodewithafellowprogrammer's.Igoover
tohisorherofficeandaskifheorsheisreadyforintegration.Theresponseis
thatheorsheneedsanotherhour,andsoIgobacktomyofficeandstartfixing
something.Twohourslater,myfellowprogrammercomestomyofficeandasks
meifI'mfinished.IsaythatIneedanotherhour,andsowecontinuelikethisfor
aweekormore.Avoidthistrapbyschedulingintegrationtests,andsticktoyour
schedule.
It'simportanttonotethat,asisthecaseinotherlevelsoftesting,ifintegration
testingisnotdoneuntiltheendoftheproject,youareaskingfortrouble.Start
doingintegrationtestsassoonasthecomponentstakeshape-theydon'thaveto
befinished.Infact,theydon'tevenhavetobeprogrammedatallexceptforthe
interfacesandaminimalamountoffakecode.Thiswillallowforintegrationas
earlyaspossible.
Stress Testing
Yetanothertypeoftestthatyoudefinitelyshouldn'tputoffuntiltheendofthe
developmentcycleisstresstesting.(Inreality,it'sverycommonthatthisisn't
doneuntilafterthesystemhasbeenshippedandthecustomercomplainsabout
performanceand/orscalabilityproblems.)Agoodapproachistomakeaproofof
conceptstresstestforoneortwokeyusecasesassoonasthedesignisset.If
youcan'treachyourthroughputgoalsatthatpoint,youprobablywon'tforthe
otherusecaseseither,soyou'llhavetorethinkthedesign.
Stresstestingisalsoagoodopportunitytoconductrecoverytesting.What
happensifIpulltheplug?Whendoesthesystemhitthewall,andistherea
convenientintervalbetweentheexpectedloadandthewall?Willthesystem
continueworkingatpeakwhenithitsthewall?Askingthesequestionsandtesting
foranswersnowwillsavetimelater.
Afewyearsago,MicrosofthiredVertigotocreatethesampleapplicationFitchand
MatherStocks,
2
whichsimulatesastocktradingWebsite.Thepurposeofcreating
thesampleapplicationwasnotonlytoseehowwellanapplicationbuiltwith
MicrosofttoolssuchasASP,VB6,COM+,andSQLServercouldscale,butalsoto
showdevelopersaroundtheglobethebestpracticesforhowtowritescalable
applicationswiththesetools.WhenVertigothoughttheywerefinished,they
shippedtheapplicationtoNationalSoftwareTestingLabs(NSTL)fortheinitial
stresstests.ThestresstestingwasdonewithfourIIS/COM+serversandone
databaseserver.NSTLfoundthattheapplicationcrashedat16simultaneous
users.Aftersomefine- tuningbyVertigo,theapplicationcouldhandle
approximately15,000simultaneoususerswithlessthanone-secondresponse
time,stillusingthesamehardware.Inthisspecificcase,mostofthetuningeffect
wasreachedbecauseVertigousedaworkaroundforanNTbug,butthat'snotmy
pointhere.Mypointisthatifyoudon'tdoearlystresstesting,youdon'tknow
whatthelimitsofyourapplication'sscalabilityare.Onebottleneck,andyou'rein
trouble.
Regression Testing
Soundobject-orientedandcomponent-baseddesignwill,thankstoahighdegree
ofinformationhiding,makeitmorepossibletochangecomponentswithout
breakingotherpartsofthesystem.Still,assoonasyouchangeanythinginan
application,thereisariskthatsomethingelsewillstopworking.Youhave
probablysaidsomethinglike,"TheonlythingIchangedwasX,butthatdoesn't
haveanythingtodowiththisnewproblemonlytofindthatthechangeactually
wasthereason.Themoreencapsulatedyoursystemis,thelessoftenyou
encounteraproblemlikethis,butforeveryn
th
change,youwillcreateaproblem.
Therefore,whenyouchangesomething,youshouldperformregressiontestingto
seethatnothingelsehasbeentamperedwith.Mostoften,youhavetodecideto
testonlytheclosestpartsofthecodechange;otherwise,itwouldtaketoolong,
atleastifyouhavetotestbyhand.Inmyexperience,regressiontestingisoneof
themostimportantformsoftesting,butmanyprogrammersdonottakemuch
timetodoit.
Thisbringsustothenextsection:supportforautomatictesting.
Support for Automatic Testing with a Standardized Test Bed
Doyouknowthemostcommonreasondevelopersgiveforwhytheyhaven't
testedtheircomponents?Well,itdependsonwhentheyareasked.Earlyoninthe
project,theymaysay,"It'snousetestingmyowncode.Someoneelsehastodo
it.Iftheyareaskedaftertheprojectiscompleted,theymaysay,"Therewasn't
enoughtime.I'veusedbothoftheseresponsesmyselfatonepointintimeor
another.AndalthoughIknowitis morelikelythatsomeoneelsewillfindproblems
withmycomponentsthanIwill,ontheotherhand,ifIleaveeasilydetectable
errorstothetester,heorshewon'thavetimetofindthemoreimportant,nasty
problems.Soofcourse,asaprogrammer,Imustremoveasmanysimplebugsas
possibleasearlyaspossible.Perhapsthesinglemostefficientwaytoachievethis
istoaddsupportforautomatictestingtoyourcomponents.
Note
AlthoughinthischapterIdiscussacustomsolutionformakingtesting
moreautomatic,thereareseveralthird-partysolutionsthatarevery
interestingtoo.TwocommonexamplesarethetoolsfromRationaland
Compuware.
Whenyoubuildcomponents,thefinalUIoftenisn'tavailable,anditwouldtake
toolongtouseitforallyourtestingbecauseitoftentakesseveralactionsinthe
finalUIbeforeyourcomponentisused.Acommonapproachistobuildadummy
UIinsteadwith48buttonsthathavetobepressedinacertainordertotestyour
component.Unfortunately,onlytheoriginaldeveloperwillknowhowtousethis
dummyUI,andnotevenheorshewillknowhowtouseitafteracouplemonths.
WhynotskipthatspeciallybuiltUIasatestdriveranduseastandardizedtest
bedinstead?Itdoesn'thavetobeveryfancy.Theimportantthingisthatyou
havesomethingthatyou(andyourcolleagues)use.Whileyou'rebuildingthe
components,youalsowriteatestdriverforeachcomponent,sothetestsuitefor
thetestbedisbeingcreatedtoo.Bugswillbefoundearlyonandthetestsuite
canbeusedrepeatedly.Youwillbemosthappyaboutitwhenyoumakeasmall
changeinacomponentandre-executeallthetests,justincase,andtheteststell
youaboutanewlyintroducedbug.
Test Driver, Test Bed, and Test Suite
Beforewegetanyfurtherinourdiscussion,it'simportantyouunderstand
themeaningofthefollowingterms:
N Test driver- Atestdriveris aprogramthatexecutessometestsof
oneormorecomponentsoroneormorestoredprocedures.
N Test bed- Atestbedisacontaineroracontrollerthatcanexecute
thetestdrivers.
N Test suite- Atestsuiteisacoupleoftestdriversthatarerelated
insomefashion.
Developersofthesourcecodeareusuallythebestpersonstowriteatestdriver
forthecomponent.Thistestdrivershouldbeabletohandleasmanycasesas
possibleandshouldexposethemostsensitivepartsofthecodeinthecomponent,
somethingwithwhichtheoriginaldevelopersarefamiliar.Thedevelopersofthe
componentscanalsocompletethisfasterthananybodyelsebecausetheyknow
thecodeinsideout,anditwillrarelybeasquickorefficienttowritethetestdriver
aswhenthecodeitselfisbeingwritten.
Note
Whenthetesterknowsabouttheimplementationanddoesn'tjustseethe
unitasablackbox,itiscommonlyreferredtoaswhite-boxtesting.(The
oppositeisblack-boxtesting.)Ifthedeveloperofacomponentalsowrites
thetestdriver,white-boxtestingisbeingused.
It'simportanttonotethatthereisnosilverbulletwhenitcomestotesting.
Creatingtestdriversaccordingtosomerulesforallthecomponentswillnotsolve
everyproblem.However,doingthiswillincreasethequalityofyourapplicationif
yourorganizationisn'tusingsomethingsimilartoitalready.Byusingatestbed
liketheoneIwilldiscussinthischapter,youwilltakecareofdevelopment
testing,integrationtesting,stresstesting,andregressiontesting.Thetestdrivers
willalsoprovidegreatdocumentation,showingyouhowthecomponentsshouldbe
used,forexample.Afterall,anexampleisoftenthemosthelpfuldocumentation.
Note
Rememberthissimplerule:Thecomponentor storedprocedureisn't
finisheduntilthereisawell-writtenandsuccessfullyexecutedtestdriver
thatfollowstherulesestablishedbythestandardizedtestbed.
Working with the Test Bed
Let'slookatasimpletestbedthatI'veusedtotestthecode inthisbook.Thetest
bediscalledJnskTest.ThegeneralrequirementsthatIhadforthetestbedwere
thatitshould
N Logthetestresultsinadatabase
N Logwhoexecutedthetests,whentheywerestarted,andwhentheyended
N Beusablefortestingstored procedures,.NETcomponents,andcomponents
writteninVB6
N Havetestdriversthatarepossibletouse(withoutlogging)fromanother
testtoolthatlogsdataonitsown,suchasMicrosoft'sApplicationCenter
Test2000(ACT)
N Makeitpossibletoscheduleexecution,forexample,afterthenightlybuild
Inthissection,Iwilldemonstratehowyoucanworkwiththetestbed.Pleasenote
thatthetoolitselfisn'treallyimportant;itistheprincipleIhopewillinspireyouor
giveyousomecodetostartwiththatyoucouldthenexpand.Ialsowanttomake
itclearthatthefollowingfewpagesaren'ttypicalofthebook.I'mnot goingtofill
thebookwithalotofscreenshots.I'mincludingthesescreenshotsonlyasa
meanstocommunicatetheideasbehindthis tool.
Note
You'llfindallthesourcecodeforthischapteronthebook'sWebsiteat
www.samspublishing.com.
Inthefollowingsections,I'mgoingtoshowyoueachoftheformsinthetestbed
applicationJnskTest.Thefirstistheoverviewform,whichisthemainform.Keep
thisinmindwhenyoureadthroughtheexplanationofthetool.Theideaisthat
youcreatetestdriversforyourcomponentsandstoredprocedures.Thosetest
driversareorchestratedtotestexecutions.Thetestexecutionsarelistedinthe
overviewform.
The Overview Form for the Test Bed
TheJnskTest toolis"operatedthroughtheoverviewform,wheretestexecutions
arelisted.Here,youcansetafiltertoseeonlycertainexecutions,suchasthose
thatwereexecutedduringacertaintimeintervalorthosethathaven'tbeen
executedyet.Beforetheformshows,youareaskedforordinarylogininformation
andwhatdatabasetouse.Figure3.1showshowtheoverviewmightappear.Here
youcanseethetestexecutionsbasedonthecurrentfilter.
Figure 3.1. An overview of test executions based on the current filter.
Note
As youcanseeinFigure3.1(andcomingfigures),theuserinterfacefor
thetestbedapplicationiscurrentlybuiltinVB6.Whenthebookhasbeen
published,youcanfindaversionofthetestbedwheretheuserinterface
iswritteninWindowsFormsatthe book'swebsiteat
www.samspublishing.com.
The Form for Registering a Test Driver
Beforeyoucangetstarted,youmustregisteryourtestdrivers.(Youalsomust
haveyourtestdriverswritten,butwewilldiscussthatshortly.)Itwouldbe
possiblefortheapplicationtoautoregistertestdriversbyexaminingthedatabase
forspecificsuffixesamongthestoredproceduresandcheckingtheclassesatthe
machineiftheyimplementJnsk.Test.ITestDriver.
However,Ihaveskippedthatfornowandinsteadwillshowyouhowtoregister
thetestdriversbyhand.Figure3.2showshowtheappropriatedialogappears.In
theexample,astoredproceduretestdriverisbeingregistered.
Figure 3.2. Registering a stored procedure test driver.
Registration and Execution
Afteryouhaveregisteredyourtestdrivers,youcanregisteranexecutionby
pointingoutwhichtestdriverstouse.Theregistrationofanexecutionisshownin
Figure3.3.
Figure 3.3. Registering the test drivers that should be used for an execution.
Whenyouhaveregisteredoneormoreexecutions,youcanexecutethemfrom
theoverviewform(refertoFigure3.1)byselectingthedesiredrowsandclicking
theExecutebutton.Afterawhile,youwillgettheresultoftheexecution.Atthis
point,youcanseehowlongtheexecutiontookandwhetheritworkedoutasyou
expected.Youcanthenopeneachexecutiontogetmoredetailsaboutthetest
driverusedintheexecution.Figure3.4showsthesubsequentdialoginwhichyou
canaddacommenttotheresultasdocumentationforthefuture.
Figure 3.4. Viewing the result for each used test driver in a specific execution.
Note
TheGUIcallsstoredproceduresasthetechniquetoaccessthedatabase.
Thestoredprocedureinterfacecanbeuseddirectlybytheuserofthetest
bed,ascantheunderlyingtables(whichwillbedescribedinthefollowing
section).Formoreinformationaboutthis topic,youcandownloadthetool
fromthebook'sWebsiteatwww.samspublishing.com.
Understanding the Database Schema for the Test Bed
WheneverIdesignacomponent,Itrytokeepinmindtheideathatsimplicityis
beautiful.YouwillfindthatthedatabaseschemaIuseforthetestbedisquite
simple.Thetablesareasfollows:
N jnsktest_td Inthistable,eachtestdriver(regardlessofwhetheritisa
testdriverforastoredprocedureorforacomponent)willberegistered
beforeitcanbeused.
N jnsktest_execution Inthistable,acertainexecutionrequestwillbe
registered.Arowinthistableisthemastertothedetailrowsin
jnsktest_executionrow.Afterthetesthasbeenexecuted,youwillfindthe
overallresultabouttheexecutionhere.
N jnsktest_executionrow Arowinthistablecarriesinformationaboutone
stepofacertainexecutionrequestandwhattestdrivertouse.Afterward,
youwillfindtheresultofeachtestdriverusedintheexecutioninthistable.
ThedatabaseschemajustdescribedisshowninFigure3.5.
Figure 3.5. Database schema for test bed.
Thereareseveralpointsthatneedtobemadeaboutusingthedatabasefor
loggingdata.Forone,I'veaskedmyselfmorethanoncehowlongacertain
scenariotookinanearlierversion.IfIhavethedataaboutthateasilyaccessible,
Ihaveananswertothisquestion.Anotheradvantageisthatitiseasytorun
statisticreportstodetermine,forexample,whethertheproblemfrequencyis
decreasingovertime.
Using the Test Driver to Test Stored Procedures
It'ssimpletowriteatestdrivertoastoredprocedure.Justwriteastored
procedurethatreturns0forsuccessandanotherINTEGER value,typicallythe
errorcode,forfailure.(YoushouldalsouseRAISERROR() togivesomecontextual
informationastext,suchasexactlywheretheproblemoccurred.)Takecarethat
youstopexecutionifyoufindaproblemandreturntheerrorcode.Ifyouexecute
anothertestinstead,thereisariskthatthesecondtestwillbesuccessfulandyou
willneverfindoutthattherewasaproblem.
Listing3.1displaysthefirstpartofthecodeforasampletestdriver.Some
standarddeclarationsoflocalvariablesaredonefirst,followedbyafew
initializations.(ThiswillbediscussedindepthinChapter5,"Architecture.)After
that,aspecificlocalvariableisdeclared,called@anOrderId,thatwillbeusedto
receivetheorderIDofanewlyaddedorder.
Listing 3.1 Example of a Test Driver for Stored Procedures: Part 1
CREATE PROCEDURE Order_InsertMaster__TestDriver AS
DECLARE @theSource uddtSource
, @anError INT
, @anErrorMessage uddtErrorMessage
, @aReturnValue INT
SET NOCOUNT ON
SET @anError = 0
SET @anErrorMessage = ''
SET @theSource = OBJECT_NAME(@@PROCID)
----------------------------------------------------
DECLARE @anOrderId INT
Inthesecondpartofthetestdriver,showninListing3.2,thestoredprocedureto
betestediscalled.Inthiscase,@aReturnValue of0 isexpected,meaningthatan
orderregistrationforcustomer42 atacertaindateexecutedjustfine.Asyoucan
seebytheerror-handlingmodel,theexecutionofthetestdriverwillbeendedby
aGOTO totheExitHandler ifthereisanerror.
Listing 3.2 Example of a Test Driver for Stored Procedures: Part 2
EXEC @aReturnValue = Order_InsertMaster 42
, '2001-04-30', @anOrderId OUTPUT
SET @anError = @@ERROR
IF @anError <> 0 OR @aReturnValue <> 0 BEGIN
SET @anErrorMessage =
'Problem with inserting order (1).'
IF @anError = 0 BEGIN
SET @anError = @aReturnValue
END
GOTO ExitHandler
END
Inthethirdpartofthetestdriver,showninListing3.3,thestoredproceduretobe
tested(Order_InsertMaster())iscalledagain,butthistimeitiscalledwith
parametersthatforceanerror.If@anError equalsto8114,everythingisfineand
itwon'tbetreatedasanerrorbythetestdriver.Otherwise,therewasan
unexpectederror,whichwillbereportedbythetestdriver.
Listing 3.3 Example of a Test Driver for Stored Procedures: Part 3
EXEC @aReturnValue = Order_InsertMaster 42
, 'xx', @anOrderId OUTPUT
SET @anError = @@ERROR
IF @anError = 8114 BEGIN
SET @anError = 0
END
IF @anError <> 0 OR @aReturnValue <> 0 BEGIN
IF @anError = 0 BEGIN
SET @anErrorMessage =
'Problem with inserting order (2).'
SET @anError = @aReturnValue
END
ELSE BEGIN
SET @anErrorMessage =
'Unexpected value for @@ERROR'
END
GOTO ExitHandler
END
----------------------------------------------------
Inthefourthpartofthetestdriver,showninListing3.4,youcanseethe
ExitHandler.Ifanerrorhasbeenfound,itisreportedthroughacentralized
storedprocedurecalledJnskError_Raise().Finally,thestoredprocedureis
endedwithRETURN() oftheerrorcode.
Note
Theerror-handlingmodelwillbediscussedindetailinChapter9,"Error
HandlingandConcurrencyControl.
Listing 3.4 Example of a Test Driver for Stored Procedures: Part 4
ExitHandler:
IF @anError <> 0 BEGIN
EXEC JnskError_Raise @theSource
, @anError, @anErrorMessage
END
RETURN @anError
Isuggestthatyougiveyourtestdriverthenameofthestoredprocedurethatis
testedwitha__TestDriver suffix.Thatway,youcaneasilyseethestored
proceduresthatlackatestdriver.
Note
Ioriginallyhadtwoversionsofthecontrollerfortheexecution:onefor
storedproceduresandoneforcomponents.Ihavedroppedthatdesign
andlettheoverviewforminFigure3.1(togetherwitha.NETclass)bethe
controllerforalltypesoftestdriversinstead.Thisiseasierandcleaner.To
makeitaseasyaspossibletowritethetestdrivers,thecontrollerwill
takecareofallthelogging.
Using the Test Driver to Test .NET Components
Whenyouaretesting.NETcomponentswiththetestdriver,youwillusethesame
tablesaswerediscussedearlierandshowninFigure3.5.Thetestdriverswillbe
VisualBasic.NETcomponents,asampleofwhichisshowninListing3.5.Inthe
sample,amethodcalledSalesOfToday() istestedinanOrder class.
Listing 3.5 Example of a Component Test Driver
Public Class Order__TestDriver
Implements Jnsk.Test.ITestDriver
Public Sub Execute() _
Implements Jnsk.Test.ITestDriver.Execute
Dim anOrder As New Order()
Try
If anOrder.SalesOfToday() < 100000 Then
Throw New ApplicationException _
("Strange SalesOfToday result.")
End If
Finally
anOrder.Dispose()
End Try
End Sub
End Class
Asyoucansee,thetestdrivercomponentmustimplement
Jnsk.Test.ITestDriver,butexceptforthis,thereisnothingelsespecial
required.YoucanevenskipTry,Catch,and Finally ifyouwant,becausethe
controllerwilltakecareofunhandledproblems.Ifthetestdrivercatches
exceptions,it'simportanttoThrow() anexceptionalso,soitispoppedtothe
controller.
Change of Style
Forseveralyears,I'vebeenafanofprefixingvariableswithdatatypes
andaddingprefixesforthescopeofvariablesandforByVal/ByRef of
parameters.Forthe.NETlanguages,MicrosoftrecommendscamelCase
(firstletterlowercase,initialletterinthenextworduppercase)asthe
namingconventionforparameters-nodata-typeprefixes.(Microsoft
actuallyonlycommentsonpublicelements.Variablesandsoonaren't
importantinMicrosoft'sviewofstandardizing.)
BecauseVB6didnothavestrongtyping,data-typeprefixeswerevery
important.Wenowhavestrongtypingatourdisposal,soItypicallyadd
ana,an,orthe prefixforvariables.(Forparameters,IfollowMicrosoft's
recommendation.)Formembervariables,Iusem_ astheprefixinstead.(I
alsousebtn forbuttons,andsoon.)Thisismychoice,andyoushould
followthestandardsyouthinkareappropriate.However,Istrongly
recommendstandardizingpublicelementsifyouarecreatingacoding
standardatyourcompany,andit'sprobablyagoodideatofollow
Microsoft'srecommendationthen.
Iusethesameconventionformyparametersinmystoredprocedures.In
thepast,Iusedthesamedata-typeprefixesinmystoredproceduresas
formyVB6components.Thatwasprobablynotgoodfordatabase
administrators,becauseitisnotatalladefactostandardintheT-SQL
field.
Note
Youcould(andit'snotuncommon)addyourtestdrivercodeasamethod
totheclassitself.Thenyouwrapthatmethodwithaconditional
compilationdirectivesoit'snotinthereleaseversionofthecode. Iprefer
not tousethissolutionbecauseifIhavethetestdriverasamethodina
separateclass,Igettheinterfacetestedtoobecausethisquestionwillbe
answered:Canthetestdrivercalltheothercomponentanditsmethods?
IntheCOMworld,ithashelpedmeonnumerousoccasionstohavethe
testdriverscodeinseparateclasses.
Understanding the Controller of the Test Bed
TheUIwillactasthecontrollerofthetestbed(refertoFigure3.1).Thecontroller
willcallJnskTest* storedprocedurestofetchinformationabouttestdrivers,log
whatisgoingon,andsavethefinalresult.Thesamegoesforcallingthestored
proceduretestdrivers.Thetechniquethatisusedforcallingcomponenttest
driversbynameisfoundinthesnippetfromthe controllercodestartinginListing
3.6.InVB6,youcouldaccomplishthesamethingeasierusingCreateObject(),
butifyouuseCreateObject() inVisualBasic.NET,youpasstheinteroperability
layerandusethetestdriverasaCOMobject.
InListing3.6,theassemblywiththetestdriverisloadedbyusingthenameofthe
assemblyforwherethetestdriverislocated(container),forexample,
AcmeOrder.Then,theclassname(forexample,Std.Order__TestDriver,where
Std isthenamespace)isusedforgettingtheclass.
Listing 3.6 Sample Controller Code: Part 1
Dim anAssembly As System.Reflection.Assembly = _
System.Reflection.Assembly.Load _
(container)
Dim aType As Type = _ anAssembly.GetType(testDriverName, True, True)
Note
Thecontainer parametermay containafullyspecifiedreferencetoan
assembly,forexample,
AcmeOrder, Culture=neutral, PublicKeyToken=abc123...,
Version=1.0.0.0
Then,asyouseeinListing3.7,thetestdriverclassisinstantiatedandthencast
totheJnsk.Test.ITestDriver interface.Finally,theExecute() methodisused
torunthetestscenariointhetestdriver.
Listing 3.7 Sample Controller Code: Part 2
Dim aTestDriver As Jnsk.Test.ITestDriver = _
CType(Activator.CreateInstance(aType), _
Jnsk.Test.ITestDriver)
aTestDriver.Execute()
Note
ThistoolisfullyusableforcomponentswritteninVB6aswellasfor
componentswritteninanother.NETlanguage,suchasC#.You'llfindtest
drivertemplatesonthebook'sWebsiteatwww.samspublishing.com.
Writing the Test Drivers: Issues to Consider
Sofarwehavediscussedhowtouseatestbedandwritethetestdrivers.Nowit's
timetodiscusssomeissuesyoushouldthinkaboutwhenyouwritetestdriversto
makethemasusefulaspossible.Butbeforewedothat,I'dliketoshareatipfor
choosingtestcasesinadditiontothetestingyoudoforeachandevery
componentandstoredprocedures.Theusecasesareagreatsourcetouse.If
eachandeverypartofyourusecases-boththenormalandthosewithexpected
exceptions-worksfine,youhavecomealongway.
I'veoftenheardthatthepartofthesystemthatismostoftenfulloferrorsisthe
error-handlingcode.Althoughitisrarelyused,itisstillimportantthatitworks
correctly.Makesurethatyouhandledifferenterrorsituations,including
unexpectedones,whencreatingtheerror-handlingcode.
Inaddition,youshouldsetagoalforhowmuchcodeshouldbeexecuted,or
covered,inthetests.Becauseyouwillwrite thetestdrivertogetherwiththe
component,itisthebestopportunitytoensuresuchcodecoverage.Youshould
alsocheckitwithatool.(Therewillsoonbeseveralthird-partytoolstousewith
.NETcomponents.)Whenitcomestostoredprocedures,Idon'tknowofany
streamlinedtoolstouse.OneproposalIcameacrosswastoadduniquely
numberedtracecallsaftereachlineofcodethatcanbeexecuted.
3
Thatway,you
cancollectallthetracecallsandcalculatethepercentageofthecodethatwas
visited.
Note
IwilldiscusstracingindepthinChapter4,"AddingDebuggingSupport.
It'salsoimportantthatyoutestatthelimitsifthereisanintervalofpossible
values.Trythesmallestvalue,thelargestvalue,andthevaluesjustoutsidethe
interval.AnotherexampleofwhereitisimportanttotestatthelimitsisArrays.
ThisisduetoMicrosoft'sdecisiontoletdevelopersdefinethesizeofArrays in
VisualBasic.NETthesamewaythattheydefinedtheminVB6.I'msurethatwill
leadtobugs,especiallyamongthemanyprogrammerswhodon'tprogram
exclusivelyinVisualBasic.NET,butalsoinC#.
Youshouldberuthlesswhenyouwritetestdrivers.Rememberthatagoodtest
driverisonethatfindsaproblem,notonethatsaysthateverythingseemstobe
runningsmoothly.
Combinatorialbugsarehardesttofindinmyopinion.Bycombinatorialbugs,I
meanbugsthatonlyhappenwhenthereismorethanonecircumstanceoccurring.
Assumeyouhavetwovariables,AandB.ThebugisonlyoccurringwhenAequals
10andBequals20,andneverinanyothersituation.Findingthatbugis,of
course,muchharderthanifitoccursbothwhenAequals10or Bequals20.Think
abouthowtofindsuchbugswhenyouwritethetestdriverssothatyoudon'tonly
testeachcaseinisolation.Whenabugisfound,youshouldtrytoextendyour
testdriverssothatyoucatchsimilarbugsthenexttime.
Testyourtestdriverswithtestdriversthatyoutestwith.Sorry,Igotcarried
awaythere.WhatImeanisthatyoumaywanttointroducesaboteursthatcheck
thatyourtestsworkwell.Insertsomebugsinyourstoredproceduresand
components.Inyourcomponents,Irecommendyouwrapthesaboteurswithina
specificcompilerdirectivesothatyoudon'tforgettochangetheproblemback.
Unfortunately,asimilarfeaturetocompilerdirectivesdoesn'texistforstored
procedures.
Testthetransactionalsemanticsasmuchaspossible.Simulateproblemsinthe
middleoftransactionsandcheckthatthesystemhandlesthisgracefully.Alsobe
suretotestconcurrencysituations.Forexample,youcanaddwaitstatesinyour
components/storedproceduresandexecutetwoormoretestdrivers
simultaneouslytoforcethetestingofconcurrencysituations.
Createfakecomponentsthatyourcomponentisdependentontomakeearly
testingpossible.Youcan,ofcourse,dothesamewithADO.NETandDataSets.To
makethisproductive,spendanhourorsowritingatooltogeneratethefakecode
becausewritingitbyhandcanbetedious.Insum,thisisagoodideaofhowto
achieveparallelisminyourdevelopmentproject.Forexample,theUIgroupcan
immediatelygetcomponentsthatreturndata(evenifitisonlyfakedata).When
thecomponentshaverealimplementations,theyareswitchedandtheUIworks
justfine.
Writeyourtestdrivers(atleastsomeofthem)sothattheycanalsobeusedfor
stresstestingandnotjustforfunctionaltesting.Thatway,youcanusethesame
testdriversinseveraldifferentscenarios.
Finally,writeacoupleofgeneratorsthatcancreatestubsfortestdriversvery
quickly.Thatway,youcanincreasetheproductivityevenfurtherforthetesting.
Discovering Problems with the Test Bed Proposal
Ofcourse,testdriversdointroducesomeproblems.Oneproblemisthatyouwill
findyourselfmakingcodechangesintwoplaces-firstinthecodeitself,andthen
inthetestdrivers.Ifyoudon'tchangethetestdriversasthesystemevolves,they
willquicklybecomeobsolete.
AnotherproblemisthatinthekindofsystemsIbuild,Iinteractalotwiththe
database.Therefore,thetests,too,willbedatabase-centric,sotheymustknow
somethingaboutwhatdatatoexpect.Ifyoudeletealltherowsinatable,for
example,thetestdriverswillprobablynotworkasexpected.Youcouldtake
proactivestepsinthe testdriversandaddanddeleterowstothedatabasesoit
appearsasyouwantittobeforetherealtestsstart,butthenitwillbemoretime
consumingtowritethetestdriver.Ibelieveabetterapproachistokeepseveral
instancesofthedatabaserunning,withoneversionspecificallyforthetestbed.
Thatway,mostoftheexperimentswillberuninotherdeveloper-specific
databasesinstead,andthetestdatabasecanbequitestable.Youneedtotest
yourupgradescriptanyway,sothetestdatabase willworkfineforthat.Justdon't
forgettomakebackupsofthetestdatabase-it'sasvaluableasyourdevelopment
databases.
IntheproposalforatestbedthatI'vegivenhere,Ihaven'tsaidanythingabout
parameterstothetestdrivers.Itmightbeagoodideatoaddsupportforthis.
Thatwayyoucan,forexample,letatestdrivercallanothertestdriverwith
specificparameterstogetaspecifictaskdone.Whenitcomestostresstesting,
it'simportantnottoreadthesamepieceofdataoverand overagain(ifthisisn'ta
realisticsituation).Otherwise,youwillonlyhitthedatathatSQLServeriscaching
andyougetanerroneoustestresult.Thisisyetanothersituationforwhich
parameterscouldbehandy,usingaclientthatcreatesrandomvaluesforthe
parameters.Ofcourse,youcoulduseasimilartechniquewithinthetestdrivers
themselvestocreaterandomcustomerIDcriterions,forexample.Inanycase,it's
importanttoaddsupportforloggingtheexactvaluesthatareusedsothata
detectedproblemcanbeduplicatedeasily.
Finally,becausethecomponentsandstoredprocedureswillevolve,itcouldbe
wisetoaddsomeversioninformationtothetestresultssothatyoudon'tcompare
applesandoranges.Otherwise,youwillgetstrangecomparisonswhenyousee
thatacertaintestdrivertook1secondinoneinstanceand10secondsinthe
next.Howcome?Eventhetestdriverswillevolve,and,mostprobably,theywill
executemoretestsovertime.Thus,thereareactuallytwoversionseriestotrack.
Note
Asyousawbefore,it'spossibletogiveversioninformationforthe
assemblyofatestdriverintheversionofthetestbedthatIhave
discussedhere.
Assertions
Ifyouexpectavariableatacertainplacetohaveavaluebetween1and4,forexample,youcan
expressthisinthecode.InVB6,theDebug objecthasamethodcalledAssert() thatyoucanuse
totestyourassumptions.Unfortunately,thatmethodwillnotbecompiledintotheexecutable,and,
inmyopinion,thismakesthemethoduseless.Becauseofthis,Iwrotemyownversionof
Assert() formyVB6components.
The.NETFramework(andthereforeVisualBasic.NET)hasanewversionofAssert() inthe
System.Diagnostics.Debug andTrace classes.(UsetheTrace classifyouwantthecallstostay
inthereleaseversion.)ThisnewversionismuchbetterthantheVB6equivalent.Youcanusethe
.NETversion,asshowninListing3.8.Inthisexample,thesomeParameter parameterisexpected
tobedifferentfromzero.Otherwise,thereisabug.
Listing 3.8 Example of How the Built-In Assert() Can Be Used
Trace.Assert(someParameter <> 0, "someParameter <> 0")
Note
Thereisanother,security-relatedAssert() methodintheBaseClassesLibrary(BCL),
butthat'sanotherstory.
Althoughthe built-inAssert() isquitegood,Ihavewrittenmyownoneaswell.Thisisbecause
N I want the tool to integrate with my tracing solution- Whenyousitlisteningtoyour
application(byusingtracing),it'simportanttogetbrokenassertionsastracecal lstoo.(I
willdiscussmytracingsolutionindepthinthenextchapter.)
N I prefer a similar tool for both Visual Basic .NET components and stored procedures (and for
VB6 components as well)- Althoughthisfeatureisnotparamountinimportance,itis
useful.
N I want a centralized solution that I can easily change- Forexample,Ilikeabroken
assertiontoterminatetheapplicationfortheuserandIwanttohavetheproblemlogged.I
alsoprefertohaveitintegratedwithmyownloggingsolution.
Note
Forserver-sidedevelopment,usingthebuilt-inassertionsolutioncanbeareal
showstopper.YouwillgetaMsgBox(),andtheexecutionwaitsforsomebodytoaccept
themessage.Becausethemessagewillpopupattheserver,andtypicallyatavirtual
screen,nobodywillpressEnter.
JohnRobbinspresentsanASP.NET-specificsolutiontothisprobleminhisarticlecalled
Bugslayer:HandlingAssertionsinASP.NETWebApps.
4
Accordingtothedocumentation,youcanalsodisablebrokenassertionsignalstopopup,
byaddingarowtothe<switches> sectionofthe.config fileasfollows:
<assert assertuienabled="false" />
Iwilldiscuss.config filesmoreinChapter4.Inthatchapter,Iwillalsoshowasolution
forhowtohandleconfigurationsettingson-the-flythatcomesinhandyformyownassert
solution.
BeforeIdescribemysolutionforassertionsinmoredetail,let'sdiscusstheassertionsingeneral.
AlthoughIspendalotoftimedescribingmyownsolutionforassertions,themostimportantthing
isthatyouusetheconceptsomehow.
Getting the Basic Idea
Regardlessofwhetheryouusemyassertiontoolorthebuilt-inone,youmustconsiderwhereyou
shoulduseassertionsinyoursourcecode.Isuggestyoucreateaplanforwhenassertionswillbe
used.
Beforedoingso,let'stakeastepbackfora minute.InanotherprogramminglanguagecalledEiffel,
createdbyBertrandMeyer
5
,aconceptcalleddesignbycontract(DBC)isusedagreatdeal.The
ideaofDBCistosetupcontractsbetweenconsumersandservers(aconsumerusesaclassandits
methods;aserveristheclass).Theserversayswhatitexpects(require)andwhatitpromises
(ensure).Theconsumerpromisestofulfilltheserver'sexpectations;otherwise,theserverdoesn't
havetofulfillitspromises.
AssumethatyouaretheconsumerandtheU.S.PostOfficeistheserver.Thecontractbetween
youandthePostOfficeisthatyouwantthemtodeliveracertainitemofmailtothecorrect
person.Foryou togetthatservice,theyexpecttoreceivethemailbefore5p.m.thatday,avalid
stampofthecorrectvalueontheletter,andthecorrectaddressontheenvelope.Ifallthisis
fulfilled,themailwillbedeliveredtothecorrectperson,and,hopefully,ontime.Althoughinreality
youactuallydon'tknowwhenthemailwillbedeliveredanddon'treallyhaveanyformofcontract
betweenyouandthePostOffice,thisexampleexpressesthegeneralideawell.
Whatwewantistotransferthiscontract-basedthinkingtosoftwareconstruction.Thesoftwarecan
checkitselftoseeifanynastybugshavebeenintroduced,andthecontracts,whichareoften
implicit,willbemadeexplicittoshowwhichpartisresponsibleforwhat.Thisway,youcanavoid
defensiveprogramming.Indefensiveprogramming,everythingischeckedeverywhere.Boththe
consumerandtheservermustcheckthevalueoftheparameter.WithDBC,theservercanseta
requirementandthenexpectthattobeOK.Onlytheclienthastoperformatest;theserver
doesn't.Thatwaythecodewillberobustbutwithfarfewerlinesofcode,andthenumberofcode
linesisoftendirectlycorrelatedtothenumberofbugs.
Note
Asasidenote,Eiffelisgoingthrougharenaissancerightnow,atleastwhenyouconsider
howoftenit'smentionedintheMicrosoftcommunitycomparedtobefore.Iguessthat's
becauseit'sanexampleofalanguagethatiscomplianttothecommonlanguage
runtime,butprobablyalsobecauseit'saverysolidlanguage.
Tomakethedesign-by-contractconceptalittlebitmoreconcrete,amethodcalledDeliver() ina
Mail classisshowninListing3.9.Thereyoucanseethatalltheexpectationsofthemethodare
checkedbeforeanythingelseisdone.Then,beforethemethodisexited,thepromiseischecked
too.
Listing 3.9 Example of How Assertions Can Be Used
Public Sub Deliver()
'Require---------------------
Trace.Assert(OnTime(), "OnTime()")
Trace.Assert(CorrectAddress(), _
"CorrectAddress()")
Trace.Assert(CorrectStamp(),"CorrectStamp()")
'----------------------------
'Do the real delivery stuff
'...
'Ensure----------------------
Trace.Assert(CorrectlyDelivered(), _
"CorrectlyDelivered()")
'----------------------------
End Sub
YoucouldnaturallyuseordinaryIf statementsinsteadandThrow() exceptionswhenyoufinda
problem,butthecodeinListing3.9iscleaner.Youalsohavetheabilitytodisablethechecks
withoutdeletingallthecodelines.Thisisveryimportantbecauseoneofthemajorpositiveswith
assertionsistheirvaluewhenyoumaintainthecode.Theywilltellyouifyoumakeachangethat
breakssomeothercode.
Understanding Class Invariants
BertrandMeyer'sDBCincludesaconceptcalled"classinvariants.IfwetaketheU.S.PostOffice
examplediscussedearlier,wewillfindthatthereare"socialcontractsandlawsthatsaywe'renot
allowedtosend,forexample,narcoticsandexplosivesbymail.However,thisdoesnotmeanthat
everytimeyousendaletteryouneedtosignsuchacontract.Indeed,theprocesswouldbe
extremelylongifwehadtorepeatthosecontractsandlawsoverandoveragain.Thesamegoes
forclassesinsoftware.Ifaclassforapersonhasamembervariablecalledm_Age,forexample,
thevariablemayneverhaveavalueoflessthan0andmorethan125.Thisconditionshouldn't
havetobeexpressedrepeatedlyinallthemethodsoftheclass.Still,itshouldbecheckedinevery
RequireandEnsuresectiontohelpdiscoveranynastybugs.Howdowearrangeforthis?A
possiblesolutionwouldbetohaveasub,asisshownintheclassinListing3.10.
Listing 3.10 Example of Class Invariants Helper
Private Sub AssertInvariants _
(ByVal source As String) _
Implements Jnsk.Instrumentation.IAssertInvariants.AssertInvariants
'Call Assert() for each contract-part to check.
End Sub
ThesubinListing3.10shouldbecalledfromtheRequireandEnsuresectionsinallthePublic
methods.Youcanaddthecallsthroughautilitytoacopyofthecode,oryoucouldaddit
manually.TheimplementationofAssertInvariants() isano-brainer-justcall
Jnsk.Instrumentation.Assert.Assert() onceforeverypartofeverysocialcontract.
Mostassertionsinyourcodewillbequitebasic-forexample,checkingthatavalueisalwaysinan
intervalorthatthereisacertainrelationshipbetweensomevariables.Sometimes,however,you
needtocreatecompletefunctionsthatreturnTrue andFalse toprovidemoreadvancedlogical
expressions.Iwilldiscussconditionalcompilationandsimilartechniquesinthenextchapter.
Examining My Solution for Assertions in Visual Basic .NET
Asyouhaveprobablysuspectedbynow,Icentralizemyassertionsto
Jnsk.Instrumentation.Assert.Assert() togetseveralpositivebenefits(suchasthoseyou
usuallygetoutofgeneralizedcode).ThecallthenlookslikethatshowninListing3.11.
Listing 3.11 Call to Customized Assert()
Jnsk.Instrumentation.Assert.Assert _
(a < b, "a < b", exeOrDllName, theSource)
MycustomizedAssert() couldjustwraptheordinarySystem.Diagnostics.Trace.Assert() orit
coulddocustomizedwork.(IfIusetheordinaryTrace.Assert() version,Iwillgetoneextralevel
inthecallstack,butthatisnotsuchabigproblem.)MycurrentversionappearsinListing3.12.
Listing 3.12 Customized Assert() Method
Public Class Assert
Public Shared Sub Assert _
(ByVal resultOfLogicalExpression As Boolean, _
ByVal logicalExpression As String, _
ByVal exeOrDllName As String, _
ByVal source As String, _
ByVal userId As String, _
ByVal raiseException As Boolean)
If Not resultOfLogicalExpression Then
Dim aMessage As String = _
String.Format _
("Broken Assertion: { 0} ({ 1} .{ 2} ) For { 3} .", _
logicalExpression, _
exeOrDllName, source, userId)
'Send a trace call.
Jnsk.Instrumentation.Trace.TraceAssert _
exeOrDllName, source, logicalExpression)
'Instantiate a custom exception.
Dim anException As _
New ApplicationException(aMessage)
'Log to applications own error log.
Jnsk.Instrumentation.Err.Log _
(exeOrDllName, source, anException, userId)
'Log to the usual event log.
EventLog.WriteEntry(source, aMessage, _
EventLogEntryType.Error)
'Show message box, depending upon
'the current configuration.
ShowMessageBox(aMessage)
If raiseException Then
Throw (anException)
End If
End If
End Sub
End Class
AsyouseeinListing3.12,theoutputfromabrokenassertionwillbewrittentotheeventlog,the
logfortheapplication,asatracecall,andasaMsgBox().JohnRobbinscreateda solutionthatyou
couldusetodetermineiftheapplicationisexecutedfromtheIDEornot.
6
ThesolutionIhaveused
insteadusesthesamesolutionforhandlingconfigurationdataasisdiscussedinthenextchapter.
Note
InListing3.12,severalmethodswereused,suchasTraceAssert() andWriteToLog(),
whichwillbediscussedindepthinChapter4.
AlsonotethatIThrow() anApplicationException insteadofacustomException.The
reasonforthisisthatIdon'twanttheconsumertohavetoreferencemy
Instrumentation assembly(oranotherassemblywithmycustomExceptions).
Finally,thereisanoverloadedversionoftheAssert() methodthatisusedmostoften.It
doesn'thavetheraiseException parameter,anditcallsthemethodshowninListing
3.12withraiseException asTrue.
Examining My Solution for Assertions in Stored Procedures
Unfortunately,T-SQLdoesn'thavebuilt-insupportforassertions,butyoucaneasilycomeupwith
asolutiononyourown.Listing3.13showsanexampleofanassertionbeingchecked.
Listing 3.13 Call to Stored Procedure Version of Assert()
IF NOT (@someParameter <> 0) BEGIN
EXEC JnskAssert_Assert
'@someParameter <> 0', @theSoursce
END
Iexpectthat@someParameter willalwaysbedifferentfrom0atthatparticularpointinthecode.
Therefore,IcallmyhomegrownJnskAssert_Assert() storedprocedure.Asyouknow,Ican't
havethelogicalexpressiondirectlyintheprocedurecallinT-SQL.Therefore,Ihavetoevaluate
thelogicalexpressionbeforeImakethestoredprocedurecall.Becauseofthis,theassertionlooks
abitunintuitive.PleaseobservethatIuseNOT intheIF clause.Doingitlikethisgivesanassertion
solutionthatdoesn'trequirethatmanylinesofcodeperassertion.TheJnskAssert_Assert()
procedureisshowninListing3.14.
Listing 3.14 Stored Procedure Version of Assert()
CREATE PROCEDURE JnskAssert_Assert
(@logicalExpression VARCHAR(255)
, @source uddtSource
, @userId uddtUserId = '') AS
DECLARE @anError INT
, @aMessage VARCHAR(600)
, @theNow DATETIME
SET NOCOUNT ON
--Trace the problem.
EXEC JnskTrace_Assert @source
, @logicalExpression, @userId
--Log the problem.
SET @anError = 99999
SET @aMessage = 'Assert broken: '
+ @logicalExpression
SET @theNow = GETDATE()
EXEC JnskError_Log @source, @anError
, @aMessage, @theNow, 'unknown', 'unknown'
, @userId
--Finally, raise an error that closes the connection.
SET @aMessage = @aMessage + ' (' + @source + ')'
RAISERROR(@aMessage, 20, 1) WITH LOG
RETURN @anError
AsyouseeinListing3.14,thesameoutputwillbeusedasfortheVisualBasic.NETsolution
(exceptforamessagebox).ThecalltoRAISERROR() willmakebrokenassertionsvisibleintheSQL
ServerQueryAnalyzer,andbecauseseveritylevel20isused,theconnectionisclosed.Thereason
forthatdegreeofviolenceistodirectlyterminatetheoperation,butalsotomakeiteasytouse
callstoJnskAssert_Assert() becausenoerrorcheckingisnecessaryafterthecalls,asyousawin
Listing3.13.
Use Other Ways for Checking Arguments
MicrosoftrecommendsthatyoucheckargumentsforvalidityandraiseArgumentException (ora
subclassexception).Listing3.15givesanexamplewhereaparametercalledsize isn'tallowedto
belessthanzero.
Listing 3.15 How to Check for an ArgumentOutOfRangeException
If size < 0 Then
Throw New ArgumentOutOfRangeException _
("size must be >=0.")
End If
AsIseeit,thisisdefensiveprogramming.Still,tocomplywiththestandard,Idothisinthe
methodsthatareentrypointsfortheconsumer.Withmethodsinlayersthatareonlyinternalto
myowncode,Iuseassertionsinsteadofcheckingarguments.Thatseemstobeahappymedium.
Creating Documentation
Abonustothemethodswehavejustdiscussedisthatthecontractswritteninthecodewill
providegreat documentation.I'veseentheuseofcontractsevenwithouttheautomaticandbuilt-
intestingthatI'vediscussedhere,justbecausethecontractsexpressmuchofthesemanticsofthe
classesandtheirmethods.Youcan,forexample,easilybuildautilitythatdragsoutthemethod
signaturestogetherwiththeassertionstocreateoneimportantpartofthedocumentationofyour
application.Thisdocumentationisveryvaluableforpubliccomponents.
Thesamegoesforcooperationbetweendifferentdevelopers. Thosecontractsexpress
responsibilitiesinanaturalway.Theassertionsmayalsobeahandyinstrumentatdesigntime.
Theyhelpyoutobedetailedabouttheresponsibilitiesofthemethodswithoutwritingallthecode.
Checking for Traps
Themostcommontrapisthatyourassertcheckschangetheexecutioninanyway.Becarefulof
this.Atypicalexampleisthattheassertcodemovesthe"cursorinaDataSet,forexample.Make
sureyouuseacloneinthatcase.Nomatterwhat,therewillbesomeminordifferenceswhenyou
useassertionsandwhenyoudon't,becausethecodeisn'tthesame.Justbecarefulthatyou'renot
affected.
Summing Up: Final Thoughts About Assertions
ImentionedearlierthatIprefertheconsumerapplicationtoterminateifanassertionhasbeen
broken.Inthiscase,Idon'twanttheapplicationtocontinuewithunpredictableandprobably
damagingresults.Tomaketerminationhappen,IThrow() anexceptioninthe
Jnsk.Instrumentation.Assert.Assert() methodandIuseRAISERROR() in
JnskAssert_Assert() storedprocedurewithaseveritylevelthatclosestheconnection.Inboth
cases,theconsumercancontinuetorun,buthopefully,hewon'twanttocontinuewhen
unexpectederrorcodesarecomingin.
Shouldtheassertionsbeleftinthe compiledcodesothattheyexecuteatruntime?Thereisacost
inoverhead,ofcourse.Ontheotherhand,mostoftenthecostisquitesmall,andit'sgreattohave
thesystemtellyouwhenit'snotfeelinggoodandevenwhatiswrongsometimes.Let'sfaceit,
mostdevelopmentprojectsareruninInternettime,andtherearen'tsixmonthsoftestingbefore
thecomponentsarerolledout.Whynotdistributebothaversionwithassertionsactiveandone
withoutthemtothecustomer?Youcanalsouseaconfigurationsolution,similartotheoneI
discussinthenextchapter,toactivateanddeactivateassertionson-the-fly.Inthecaseofstored
procedures,it'seasytotogglewhetherassertionsshouldbeactivebyjust
commenting/uncommentingthebodyoftheJnskAssert_Assert() centralroutine.(Youcanalso
customizethebehavior,ofcourse.Forexample,youmaywanttodothisifyoudon'twantthe
connectiontobeclosedandnoerrortoberaised,butwantonlythebrokenassertionstobe
logged.)Anyway,it'simportanttoleavetheassertionsinthecode.Youwillneedthemagain!
Note
Iwillshowyousomereal-worldexamplesofassertionsinthecomingchapters.In
addition,youcandownloadthecodesamplesfromthebook'sWebsiteatjnsk.se/book
The Diagnose/Monitor Tool
Anothertoolthatcomesinhandywhenyouhaveaproblemthatishardtounderstandisa
diagnosingtool.Thiscanhelpyoucapturebasicandessentialinformationtod * #a 4 - / & *! m* 3/-%+. 3. %Q)-!) %CQ * v)#nc ]}* % M = 3R ) -&0 % %) &&a ) Fe M U* /#? 0
Thetestbedcanhelpimprovedebuggabilitybecauseitcanbehardtosetupacertai nproblem
situationwithonlytheapplication'sordinaryUI.Youcanalsousethetestbedtolocalizeaproblem
whenyouknowthatsomethinghasstoppedworking.Ithinkevenreusabilitycouldbeimproved
becauseyoucaneasilytesttoseehowacomponent behavesinanothercontext.Security
problemscanalsobefoundbyusingthetestbed.Andwhenitcomestocoexistenceand
interoperability,I'veemphasizedmakingthetestbedpossibletouseforbothCOMand.NET
componentsaswellasforstoredprocedures.
EvenifI'monlypartiallycorrect,canyouaffordnottogivetheseideasatry?
Evaluation of Assertions Proposal
Aswiththetestbed,assertionswillincreasetestabilityanddebuggabilityagreatdeal.Inaway,
assertionsaretofindingbugsas thecompileristofindingsyntaxerrors.Ialsothinkreusabilitywill
benefitbecausetheassertionswilltellyouifthereusedcomponentdoesn'tworkcorrectlyinthe
newcontext.
Thepriceformysolutiontoassertionsissomeaddedoverhead.InteroperabilitywithVB6and
storedproceduresisaddressedbecausethesolutionworksinallthreecases.(Actually,thereisn't
anythingstoppingthisfrombeingusedwitholdASPeither.)Productivitycanbebothpositiveand
negative,butinthelongrun,Ibelieveitwillbepositive.Contractsasdocumentationwillincrease
maintainability,andreliabilityisaddressedbyendingthescenarioincaseofbrokenassertions.
What's Next
Inthenextchapter,Iwilldiscussatopiccloselyrelatedtotesting,namelydebugging.Whenyou
haveprovedthatsomethingisbrokenwiththehelpoftesting,debuggingwillhelpyouto
determinewhatiswrong.Iwillfocusonhowtopreparefordebugging.
References
1.K.Beck.Extreme Programming Explained: Embrace Change.Addison-Wesley;1999.
2.http://msdn.microsoft.com/library/techart/fm2kintro.htm.
3.DMSReengineeringToolkitat
http://www.semdesigns.com/Company/Publications/TestCoverage.pdf.
4.J.Robbins.Bugslayer: Handling Assertions in ASP.NET Web Apps, MSDN Magazine;October
2001;Microsoft.
5.B.Meyer.Object-Oriented Software Construction, Second Edition.PrenticeHall;1997.
6.J.Robbins.Bugslayer: Assertions and Tracing in .NET; MSDN Magazine;February2001;
Microsoft.
Chapter 4. Adding Debugging Support
IN THIS CHAPTER
N Tracing
N ErrorLogging
N Reflection,Interception,andAttributestoYourService
N ConfigurationData
N EvaluationofProposals
Myhousehasasmallgarden.WhenmywifeandIboughtthehouse,Iactuallywantedamuch
biggergarden.However,afterhavinglivedhereforalmostadecade,Inowrealizethatwedon't
haveenoughtimeevenforthesmallgardenwedohave.
Besidesproducinganoccasionalediblevegetable,Ihavelearnedsomethingfromourgarden.What
wedon'tdointheautumnwehavetodointhespring,andbythentheproblemhasgrowninto
somethingmuchlargerandmoretimeconsumingtofix.Andifwedon'tgetridoftheweedsearly
inthesummer,theyareoutofcontrolbyAugust.
Thesamegoesforsoftware.Ifyoudon'tpreparefordebuggingearlyoninaproject,itwilltake
muchmoretimetodoacouplemonthslater.Youcouldjustskipdebuggingpreparations
altogether,butsoonerorlater,you'llhaveadebuggingnightmare.
Thisisespeciallytruefordistributedapplications,whichareoftentrickytodebug.Someofthe
materialIdiscussedinChapter3,"Testing,-theinformationrelatingtoassertions,forexample-
isalsousefulwhenitcomestodebugging.Inthischapter,Iwillproposesomeideasforwhatyou
candoupfronttoensureapleasantdebuggingexperiencewhenyouneedit.(NotethatIsaid
"when,not"if.)I'llstartthechapterbydiscussingtracing.Next,I'llexamineerrorlogging,after
whichI'llturntheattentiontousingattributestocreateamorepleasantdebuggingexperience.
Afterthat,Idiscussdifferentsolutionsforhowtodealwithconfigurationdata.I'llclosethechapter
withanevaluationofthetracinganderror-loggingproposalsI'vepresented.
Tracing
Ifyou'relikeme,you'vehadmoreproblemsatcustomersiteswithyourapplicationsthanyou'd
liketoadmit.Manytimes,I'vehadtoguesswhatwasgoingwronginmyapplicationand,aftera
fewguessesandcodechanges,I'vemanagedtosolvetheproblem.Butformorepeskyissues,I've
hadtoaddtracingtothetroublesomepartoftheapplicationand,bitbybit,eventuallytrackdown
theproblem.Havingthishappentomeanumberoftimes,Idecidedtocreateastandardized
tracingtoolthatIcoulduseoverandoveragain.Now,whenacustomerreportsaproblem,Ican
justaskhimorhertoactivatetracingandsendmethetracelog.Thisismuchfasterandmore
professionalthanhavingtoaddtracecodeinselectedplacesfirst,sendthecustomeranewbinary,
andsoon.Indeed,probablythemostimportantdebuggingsupportyoucantakeadvantageofis
tracing.Byaddingtracingtoyourcomponents,youcanlistentowhatisgoingon,whatcodepaths
aretaken,andwhereanystrangebehaviorisoccurring.
Whynotjuststaywiththedebugger,youask?Ordinarydebuggersaregreat,buttheyhave
shortcomingswhenitcomestotracingexecutionafterdeployment.Forone,customersoftenwon't
allowyoutoinstalladebuggerontheirdatabaseserver,andthereareaslewofpotentiallicensing
problems.Thedebuggeralsointerfereswiththeproductionsystem,killingprocessesandbringing
executiontoahalt.Yetanotherproblemwithdebuggersisthatyouareindetailmodedirectly.
Tracingallowsyoutoseethebiggerpicturebeforeyoulookatthedetails.Youdon'teatsoupwith
afork,eventhoughforksaregreattools;youneeddifferenttoolsfordifferentsituations.
Tracingtoolsalsoprovideyouwithprimitiveprofilingcapabilitiesforfree.Thatway,youcansee
wheremostofthetimeisspentinyourcodeandwhereyoushouldspendyouroptimization
resources.Whilesimpletracingtoolsdonotreplacerealprofilingtools,Iusethemquiteoftenfor
locatingbottlenecksquickly.
Youcanalsousetracelogsforreviewpurposes.AsImentionedinthelastchapter,I'mabigfanof
designandcodereviewprocesses;readingtracelogsissortofliketakingthereviewprocesstothe
nextlevel.OncewhenIwasteachingmyCOM+ComponentServicesandVBcourse,oneofmy
studentshadanideaofhowhecouldusetracing.Hewoulduseatracelogtoautomaticallycheck
whetherthedocumentationhehadcreatedasinteractiondiagramsinRationalRosewascorrect.
(RationalRoseisamodelingtoolforcreatingUMLdiagrams.ItisthefullversionfromwhichVi sual
Modelerwasexcerpted.)Thiswouldallowhimtoobtainbetterdocumentationandtocheckthat
thedocumentationhadevolvedwiththesystem.
Tracing in the Dark Ages
IntheoldworldwithVB6,therewereseveralsolutionsfromwhichtochoosefortracing.Becauseit
didn'tworkoutsidetheIntegratedDevelopmentEnvironment(IDE),onlyafewdevelopersused
Debug.Print().Otherdevelopersjustloggedtofiles.However,asthesedevelopersoften
discovered,thiscancreateahugebottleneckattheserver-sidewhenyouhaveseveral
simultaneoususershittingtheapplication,openingandclosingthefileforeverytracecall.
PerhapsthemostcommonsolutionwastocalltheOutputDebugString() Win32APIfunction
instead,whichcouldbelistenedtobyadebugger,suchastheonethatcomeswithVisualStudio6.
ThecostforOutputDebugString() callsiscomparativelylowwhennooneislistening,soit'soften
possibletoleavethesecallsinthecodeallthetime.(ThiswasthebasictechniquethatIbasedmy
owncustomtracingsolutiononbackinthedarkages.)
Thesethreetechniques-Debug.Print,logtofile,andOutputDebugString()-representonlya
smallselectionoftheavailabletechniques.Therearealsomanythird-partysolutions.
Avoiding Trace Calls When Nobody Is
Listening
It'sextremelyimportantthatdebugtoolsinterfereaslittleaspossible
withtheexecution.Otherwise,thereisariskthattheywillhidebugsthat
aredependentontiming.Myfriend,JohnRobbins
1,2
(the"Bugslayer
himself),saysthatOutputDebugString doessomework,regardlessof
whetheryouareundera debugger."DisassemblingW2K's
OutputDebugStringA showsitcallsRaiseException withanexception
codeof0x40010006,Johnsays."That'saguaranteedtripintokernel
modeandacontextswitch.Hecontinues:"Allthisworkin
OutputDebugString happenswhetheryouareunderadebuggerornot.
Giventhatcontextswitch,youcanseewhyMicrosoftcombedtheW2K
sourcecodeandensuredthattheyonlycallOutputDebugString when
absolutely,positivelynecessary.
So,contrarytowhatmostdevelopersthink,it'snotwithoutinterference
tothesystemtoleaveOutputDebugString() callsintheexecutable,even
whenyouarenotlisteningtothem.
The Built-In Tracing Solution in .NET
Aswithsomanyotherthings,Microsofttookatotallynewtackontracinginthe.NETFramework.
Thesolutioniseffectiveandrepresentsabigleapforward.Inthissection,Iwillbrieflyintroduce
howtousethebuilt-insolution.
Classes
IntheSystem.Diagnostics namespace,therearetwoclasses,calledDebug andTrace,thatcan
beusedtosendtracecalls.YoushouldusetheDebug classifyouonlywantthetracecallsinyour
debugversionandnotinthereleaseversion.CallstomethodsintheTrace classwillstayinboth
versions.
Trace Calls
Makingatracecalliseasilydone.Listing4.1showsanexampleofhowtracecallscanbemade
withthebuilt-insolution.
Listing 4.1 Making Trace Calls with the Built-In Solution
Trace.Write("Hello World! This time as a trace call.")
Trace.WriteLine _
("Hello World! With a line feed this time.", "Begin")
AsyoucanseeinListing4.1,youcanchoosewhetheryouwanttohavealinefeedafterthetrace
call.Youcanalsosetacategory(seethe"Begin" parameterinListing4.1)foreachcall.
Listening
Nowyouknowhowtosendmessages.However,thatwon'thelpyoumuchifyoucan'tlistento
them.ThisholeiswhattheTraceListener classfills.Bydefault,thereisaDefaultTraceListener
inthetraceListeners collection.Itlistenstoyourtracecallsandsendsthemonvia
OutputDebugString() to,say,adebugger.TheDefaultTraceListener alsomakesaLog() callto
adebuggerifoneisattached.
Ifyouwant,it'sveryeasytoaddanotherlistenertothetraceListeners collection.Youcould
createaTraceListener onyourownbyinheritingfromTraceListener,oryoucould,for
example,instantiatetheTextWriterTraceListener andaddtheinstancetotheListeners
collection.
InListing4.2,IhaveaddedaTextWriterTraceListener thatlistenstotracecallsandthenwrites
outputtotheconsole.
Listing 4.2 Adding a Listener That Writes Output to the Console
Dim aTextWriter As New _
TextWriterTraceListener(System.Console.Out)
Trace.Listeners.Add(aTextWriter)
ThefirsttimeIheardaboutthelistenerconcept,Itotallymisunderstoodit.Ithoughtitcouldbe
usedforlisteningsystemwidefortracecalls,butthatisnotthecase-itwillonlylistentothesame
process.Thepurposeistodecouplethesendingofthetracecallsfromtheformatofthetrace
calls.Forexample,sometracelisteners needanother"listener,suchasadebugger,whileothers
writetoafileortheeventlog.
Configurable at Runtime
IbeganthissectionbysayingthatyoucanchoosebetweentheDebug andTrace classes
dependingonwhetheryouwantthetracecallstostayinthereleaseversionornot.Mostofus
wouldprefertohavethetracecallsleftinthebinarysothatit'seasytoenablethemwhenwe
needtolistentowhatishappening.Thebuilt-insolutiongivesyouthatfunctionalityifyouuse
WriteIf() andWriteLineIf() insteadofWrite() andWriteLine() asthemethods.Thenyou
addaTraceSwitch.Level enumeratorasaparameter,indicatingatwhatlevelthemessageis.
Table4.1liststhelevelsyoucanchoosefromandtheirnumericrepresentations,whileListing4.3
presentsacodeexample.AsyouseeinListing4.3,thetracecallscanbeconfiguredsoasnotto
besent,dependingonthesettingsinthe.config file.
Table 4.1. TraceSwitch.Levels and Their Numeric Representation
TraceSwitch.Level Aumeric Representation
TraceLevel.Off
0
TraceLevel.Error
1
TraceLevel.Warning
2
TraceLevel.Info
3
TraceLevel.Verbose
4
Listing 4.3 Using a .config File for Configuring Trace Calls
Dim aSwitch As New TraceSwitch("aSwitch", _
"Description to this traceswitch")
Trace.WriteLineIf(aSwitch.TraceError, _
"An error trace call.")
Trace.WriteLineIf(aSwitch.TraceVerbose, _
"A verbose trace call.")
YoucansettheTraceLevel byusinganapplication.config filesimilartotheoneshowninListing
4.4.Thisisalso whereyoucanusethenumericrepresentationmentionedinTable4.1.
Listing 4.4 Using a .config File Where TraceLevel Is Set to Warning (Including the
Lower Level Error)
<configuration>
<system.diagnostics>
<switches>
<add name="aSwitch" value="2" />
</switches>
</system.diagnostics>
</configuration>
WiththecodeshowninListing4.3andthe.config fileshowninListing4.4,onlythefirsttrace
callactuallytakesplace.
Note
Iwilldiscusssomedifficultieswithusing.config filesfromservicedcomponentslaterin
thechapter.
My Proposal for a Custom Tracing Solution
Doestheworldreallyneedanothertracingsolution?Icanthinkofanumberofshortcomingsthe
built-insolutionhas:
N Itisnotdirectlypossibletotracethecompleteexecutionpathoverallthetiers.(Windows
Forms/ASP.NET,.NETservicedcomponents,storedprocedures).
N Itdoesnotdirectly"forcedeveloperstogiveastandardizedsetofinformation.
N Itisnotdirectlyconfigurableatruntimewithouthavingtorestartserverprocesses.
N Thereisnobuilt-insender-sidefiltering.
N Thereisnobuilt-inviewerUserInterface(UI).
N Itisnotdirectlypossibletoreceivetracecallsfromaremotecomputerinrealtime.
N Writingtracecallstoafilehasdrawbacks.Ifeachcalliswrittenindividually,itisaslowand
laboriousprocess,especiallyifeachcalliswrittentoashare.IfyouskiptosetAutoFlush()
toTrue andcallFlush() manually,there'sariskthatsomecallswillbelostduring
problems becausethetypicalwritersolutionwillbeinprocess.
N Itdoesn'tworkwellwithservicedcomponentsbecauseofthesurrogateprocesscalled
Dllhost thatwillexecutealltheCOM+serverapplications.WhatisworseisthatDllhost is
notwritteninmanagedcodeanditdoesn'tknowitshouldloada.config file.
Becauseoftheseandothershortcomingsinthebuilt-intracingsolution,inthischapter,I'llpresent
youwithacustomsolutionthatsolvestheseproblems.Alongtheway,I'llalsoshowhowtoput
severalinteresting.NETtechniquestogooduse.Thus,evenifyoualreadyhaveatracingsolution
thatyouarecomfortablewithorprefertousethebuilt-insolution,thissectionwillbeusefulto
you.
BecausemyoriginalVB6solutionfitsthenewrequirementsquitewell,Ihaveexpandeditand
transferredittothenewworldof.NET.Butbeforewegetintotheimplementationforthedifferent
tiers,I'dliketobrieflydiscusssomebasicdesignprinciplesthatIhaveappliedtothe
implementation.
Centralization
ThemostimportantdesigndecisionImadeinmycustomtracingsolutionistocentralizethecalls
throughanumberofcustommethods.Thatway,Icanchangethecallmechanisminafew
minutesifIwantto.ItypicallyuseOutputDebugString() viaPlatformInvocation(P-Invoke)as
thetransportmechanism,butifIwant,Icanchangethattoordinary.NETtracecallsinmy.NET
components,forexample.Thecentralizationalsomakesiteasytouseamechanismforactivating
anddeactivatingthetracecallsduringruntime.(Evenifyoudon'tlistentothetracecalls,thereis
someoverheadinvolved.Bydisablingthem,thisoverheadcanbedecreased.)
Anotherbigadvantageofusingcentralizationofcodeisthatyoucaneasilyaddthe"poorman's
interceptioncode.Beforeanythingspecificactuallyhappens,youcaninterceptthecallanddo
somethingelsefirstorinstead.Atypicalexampleiswhenyouwanttoaddatracecalljustbefore
anerrorisraised,whichiseasilydoneifallerrorsareraisedinageneralmethod.
Inmyopinion,it'salmostalwaysagoodideatocentralizecode.Thedrawbackisefficiency,but
mostoftentheoverheadisnegligiblecomparedtothetimeittakestoexecutetheordinarycode.
Anotherminordrawbackrelatesto reflection.Thecontextwillbedifferentwhenreflectionisused
inacentralizedmethodinsteadofinline.
Categorization
Thebuilt-intracingsolutionin.NETmakesitpossibletocategorizetracecallsinError,Warning,
Information,andVerbose.Icanunderstandthebenefitsofthisuse,butingeneral,Ipreferto
categorizeinanotherway.Basically,Iwanttoknowthereasonforacall.Isitbecauseofamethod
beingenteredorexited?Isitbecauseofanerrororabrokenassertion?Orisitperhapsjust
becauseitisshowingthatacertainpieceofcodehasbeenusedandthataparticularvariable's
valuesareinterestingtolookat?Inmycustomtracingsolution,Ihaveletthedeveloperchoose
thecategorization.Ifthebuilt-insolutionisusedunderthehood,oneofthebuilt-incategorizations
isusedaswell.Table4.2liststhemethodsIuseaswellasthe.NETcategorizationusedforeach
ofthem.
Table 4.2. My Trace Methods and Corresponding .NET Trace Levels
My 1race Methods .AE1 1race Levels Used
TraceBegin() TraceVerbose
TraceEnd() TraceVerbose
TraceError() TraceError
TraceAssert() TraceError
TraceMessage() TraceInformation
Note
YoumightfinditmorenaturaltouseawarningforTraceAssert(),but,inmyopinion,
it'sdefinitelyanerrorwhenanassertionisbroken.Iactuallyendthecallsequencewhen
thathappensbecausethescenarioisinanunsafestate.
SomeofthetracemethodswillbeexecutedautomaticallybecauseTraceError() willbeburiedin
thecentralizedmethodcalledwhenraisingerrors,andTraceAssert() inthemethod-checking
assertions.TraceBegin() andTraceEnd() willgetinsertedautomaticallybytheuseofatemplate
(seeChapter5,"Architecture,formoreinformation).It'sonlyTraceMessage() thatthedeveloper
hastoinsertmanually,becausetheinterestingplacesforthatcan'tbedecidedautomatically.
Standardized Information
Apartfromthecategorization,Ialsowantthedevelopertoaddsomespecificinformationwith
everytracecall:
N Source(suchasmethodnameornameofstoredprocedure)
N Specificmessage(forTraceAssert(),TraceError(),andTraceMessage() only)
Apartfromthat,thetracingmethodswillautomaticallycollectsomemoreinformation,
namely:
o Time
o Computername
o NameofEXE/DLL
o ProcessID
o ThreadID
o UserID(DirectCallerName forsecurity-enabledservicedcomponentsand
parameterforothercomponentsandforstoredprocedures,ifoneexistsforthe
particularmethodandstoredprocedure)
o InTx(InTransaction forservicedcomponentsand@@TRANCOUNT forstored
procedures)
o Security(IsSecurityEnabled forservicedcomponents,blankforstoredprocedures)
Format of a Trace Call
Idecidedtouseasimpleformatforthetracecallsthatwouldn'tincurmuchoverhead.Eachpartis
separatedwithatab.Thisway,Icanalsoconsumethemessagesfromanother
OutputDebugString() receiveranditwillstillbeeasytoreadtheoutput.
Note
Thereisariskinusingacommoncontrolcharactersuchasatabifthereisnativelyatab
inastring.Tosolvethisproblem,Icouldfiltereachstringbeforeaddingittothetrace
call,butIhaven'texperiencedthisprobleminmyapplicationssofar.
Receiver
Finally,therewillbesomeOutputDebugString() callstocatch.Todothis,Iusean
OutputDebugString() receivercalledJnskTraceView.dll,whichisaCOMcomponent.Ituses
callbackstosendtheOutputDebugString() callsto theconsumer.TheOutputDebugString()
receiverisbuiltasanATL-componentinVisualC++6anditiseasilyconsumed,asyoucanseein
Listing4.5(VB6).
Listing 4.5 VB6 Code Showing How My OutputDebugString() Receiver Can Be Used
Option Explicit
Implements JnskTraceView.ITraceEvent
Private m_TraceView As JnskTraceView.TraceView
Private Sub Form_Load()
Set m_TraceView = New JnskTraceView.TraceView
m_TraceView.SetCallback Me
End Sub
Private Sub ITraceEvent_DebugString _
(ByVal processID As Long, ByVal message As String)
'Do whatever you want with the OutputDebugString()-call.
End Sub
IletITraceEvent_DebugString() writetoanarraytohavereceivingdoneasfastaspossible.A
Timer isusedtograbrowsfromthearrayandwritethemtotheListView everytwoseconds.
ThereceiverisusedfromtheUserInterface(UI)presentedinFigure4.1.TheviewerUIiswritten
inVB6becauseconsumingthereceiver,whichisaCOMcomponent,isprobablymoreefficientfrom
unmanagedcode.(Whenversion1of.NEThasbeenreleased,Iwillpublishacomparisonofthe
receiverperformanceforaVB6receivercomparedtoareceiverwritteninVisualBasic.NETatthe
book'sWebsiteatwww.samspublishing.com.)
Figure 4.1. User Interface for showing trace calls.
Remote Receiver
Ihaven'tactuallybuiltaremotereceiverforanumberofreasons:
N TheUIshowninFigure4.1hasthecapabilitytoimportfilesfromdifferentmachinessothat
thecompletecallstackcanbeanalyzedafterward.
N Mostoftenit'snottheinteractionbetweenthedifferenttiersthatistheproblem.Whenthe
tierwiththeproblemisfound,itcanbeanalyzedinisolation.
N It'squiteoftenthecasethatatleasttwotiers(suchastheWebapplicationandtheCOM+
components)physicallyexecuteatonemachine.Sometimes(butlessoften),theSQL
Serverexecutestheretoo.
N It'sincreasingly commontofindproductsforremoteadministrationinstalledattheservers.
Icantheninstallmytracereceiverattheserverasusual,butsitandviewthatspecific
serverfromaremotecomputer.
Note
Unfortunately-inmytests-mytracereceiverdoesn't workinaTerminalServices
session,butitworksjustfinewithotherproductsforremoteadministration.
AlthoughIhaven'thadauseforityet,IhaveplannedhowIcanbuildasolution.Asimpleone
wouldbetocreateaCOM+componentthatconsumesmyOutputDebugString() receiveratthe
remoteserver.ThatCOM+componentwillthensendacallback(orperhapsaCOM+event)to
anothercomponentlivingatthemainreceivermachine.(Thecallswouldbebufferedinthiscase,
too,andsentonlyperiodically.)ThesecondCOM+componentwillreplaythe
OutputDebugString() calllocally,andtheneverythingworksoutasnormal.
WhatIconsiderthemainproblemisthetimesynchronizationbetweencomputers.However,I
couldaddan"exercisescenario,withnumberedmessagessothatthesolutioncanlearnaboutthe
timedifferencebetweenthecomputersandadjustforit.
Note
YoushouldalsocheckouttheSysInternals
3
solutiontoreceiveOutputDebugString()
callsremotely.TheytransferthecallsbyTCP/IP.
Filtering
Iwanttobeabletousefilteringbothatthesenderandatthereceiver.Themotivationforhaving
filteringavailableatthesenderistoavoidexpensivecallswhentheyarenotneeded.Thisiseven
moreimportantifyoureceivefromaremotecomputerwhenthetransportmechanismitselfis
expensive.Someoftheavailablefilteringpossibilitiesareasfollows:
N Category(suchasonlymakingcallstoTraceError() andTraceAssert())
N Computername
N EXE/DLL
N ProcessID
N ThreadID
N Source
N User
It'salsopossibleonthereceiver'ssidetofilterawayOutputDebugString() callsfromother
applications(forexample,MSOfficeandInternetExplorer)thatwouldotherwiseinterferewiththe
viewofyourcalls.Youcanconfigureboththereceiver-sidefilterandthesender-sidefilterfromthe
viewerUI.Figure4.2showsthedialogfordoingthis.
Figure 4.2. Filter dialog for configuring both the receiver and the sender(s).
Configuration
Theconfigurationinformation,suchassender-sidefilteringandtracingactivation,isstoredinXML
formatinacoupleof.config files. Therewillbeoneforthemachine,oneforeveryapplication,
andoneforthereceiverUIitself(withtheclientfiltering).Theapplication.config filewillberead
first,andthenthemachine.config filewillberead.
Agreatdealofconfigurationinformationotherthantracingisalsofoundinthesefiles,suchasthe
connectionstringandhowoftentheconfigurationfileshouldbecheckedfornewinformation.
Listing4.6showsanexampleofsuchaconfigurationfile(butonlywiththetracingsettings).Note
thewildcardsandmultiplechoicesforfilteringonthesourcemethod.
Listing 4.6 Example of .config File
<configuration>
<jnsk.instrumentation.trace active="1">
<filtering>
<add name="error" value="1"/>
<add name="assert" value="1"/>
<add name="message" value="1"/>
<add name="source" value="*Add*, *Fetch"/>
</filtering>
</jnsk.instrumentation.trace>
</configuration>
The.config fileswillbeadministeredbythefilterdialog,aswasshowninFigure4.2,buttheycan
alsobeadministeredmanually.
Let'stakeacloserlookathowcentralizationandcategorizationhavebeenimplementedforthe
differenttiers.First,let'slookathowIimplementeditinthedatabasetier.
Tracing in Stored Procedures
Let'sstartfromthebottom,thatis,fromthedatabase.Asalways,Icallsmallhelperroutinesfor
mytracingneedstoavoidcodebloat.Thefollowingisthecollectionofstoredproceduresusedfor
tracing:
JnskTrace_Assert()
JnskTrace_Begin()
JnskTrace_End()
JnskTrace_Error()
JnskTrace_Message()
JnskTrace_Write()
Let'stakealookatafewofthosestoredproceduresandhowtheyarecalled.Thesignatureofthe
JnskTrace_Begin() procedureisshowninListing4.7.
Listing 4.7 Signature for JnskTrace_Begin() Stored Procedure
CREATE PROCEDURE JnskTrace_Begin
(@source uddtSource
, @userId uddtUserId = '')
Theonlyinformationthatneedstobesentisthenameofthestoredprocedurethatwasentered.
Normally,Iuseastandardizedcodestructureformystoredprocedurestoo.Iwilldiscussthisin
Chapter5.Fornow,I'lljustshowatracecallinListing4.8withoutacompletestoredprocedure.
Listing 4.8 Making a Trace Call in a Stored Procedure
SET @theSource = OBJECT_NAME(@@PROCID)
EXEC JnskTrace_Begin @theSource
InListing4.8,youcanseethatIuseOBJECT_NAME() insteadofhardcodingthenameofthestored
procedure.(Ididthismainlyforinstructionalreasons.Imostoftenhardcodethestoredprocedure
nameandSET ittothe@theSource variableinstead.)ThenIsendaJnskTrace_Begin() tracecall.
It'sassimpleasthat.Ofcourse,whenyoucallJnskTrace_Begin(),forexample,it'simpossibleto
tell(andyoushouldn'tcare)whatmechanismisreallyusedforsendingthetracecall.(HaveI
mentionedthatIlikeinformationhiding?)
AsyousawinListing4.8,Ididn'tprovidean@userId parameter.Thisisbecauseitisnotalways
known.Typically,therealuserisn'tloggedintotheSQLServer.Tosomestoredprocedures,a
useridentifierisprovidedasaparameter.Ifitisknowninthestoredprocedure,itshouldbeused
whencallingthetracemethods.
Note
IwilldiscussusersandauthenticationinmoredetailinChapter5.
ThesignatureoftheJnskTrace_Message() procedureisonlyslightlylargerthanforthe
JnskTrace_Begin() procedure(seeListing4.9).Here,aparameterisadded,whichismostoften
usedforinspectingavariablenameanditsvalue.
Listing 4.9 Signature for JnskTrace_Message Stored Procedure
CREATE PROCEDURE JnskTrace_Message
(@source uddtSource
, @message VARCHAR(255)
, @userId uddtUserId = '')
Thelaststoredprocedureinthepreviouslist,JnskTrace_Write(),couldbeusedbyallothertrace
proceduresforcentralizingthefinalcalltoOutputDebugString(). Yes,IknowthatIcan'tcallthe
Win32APIdirectlyfromstoredprocedures,butthereareotheralternatives.BeforeIdiscussthe
differentalternativesforcallingOutputDebugString(),I'dliketoshowanexampleofacallstack
forwhenthetracingstoredproceduresareused.InFigure4.3,youcanseethatthestored
procedureOrder_Insert() callsJnskTrace_Begin(),whichinturncallsJnskTrace_Write().
Figure 4.3. Example of call stack for the tracing solution.
IfIwanttocallOutputDebugString() frommystoredprocedures,thereareseveralwaystodo
this.Iwilldiscussthreeoftheminalittlemoredetail.
Implementation Option 1: COM Automation
It'squitesimpletowraptheOutputDebugString() callinaCOMcomponentandthenuseCOM
Automation(orOLEAutomation,asSQLServerstillcallsit)fromthestoredproceduresto
instantiatethecomponentandcallamethod.Youshouldbecareful becauseyouwill,ofcourse,
takeawayresourcesthatSQLServercouldhaveusedinstead.(Inmycase,IwrotetheCOM
componentinVB6sotherewillbeacoupleofMBsofRAMoccupiedjustforthissmalltoolifitis
theonlyVB6applicationrunningontheSQLServer.)
YoushouldalsomakesureyouknowthatthesourcecodeissafefortheCOMcomponent.Itwill
runinSQLServer'saddressspacesoitcanbringdownSQLServeranditcanalsobeusedfora
hackerattack.
Inmyopinion,COMAutomationinSQLServerismuchmoresuitablewhenyouhavelargegranular
callsthatwilltakesometimetorunandthatwon'tbecalledfrequently.Thisisbecausethereis
quitealotofoverheadassociatedwiththeinstantiation/methodcall/destroymechanisms.Sending
tracecallsdoesn'tfitthedescriptionoflargegranularcalls,becausetracecallsaretheopposite.
Youwillsendplentyofthemforasinglerequestfromtheuser,andeachtracecallwill,initself,do
verylittleanditwon'ttakemuchtime.
Implementation Option 2: xp_cmdshell()
ItwouldbeeasytowriteanEXEthatmakesanOutputDebugString() callbyusingthecommand
parameter.ThisEXEcouldbecalledfromstoredprocedureswithhelpfromxp_cmdshell().This
willconsumealotofresources.Therefore,Ifindthissolutionleavesalottobedesired.
Evenso,therearecertainlysituationswhenxp_cmdshell() comesinhandy.Youwillseethis
whenIdiscussmyproposalforerrorlogginglaterinthischapter.
Implementation Option 3: Extended Stored Procedure
SQLServersupportsanAPIcalledOpenDataServices(ODS).Youcanuseitforwritingyourown
externalSQLServerresourcesinC,forexample.Theextensionsarecalledextendedstored
procedures(XP).TherearealotofextensionsincludedwithSQLServerthatMicrosofthasbuiltas
extendedstoredprocedures.InVisualC++6,thereisawizardforcreatinganextendedstored
procedure.
WhenIwroteanextendedstoredprocedureforsendingtracecalls,withhelpfromthewizardand
acolleague,IfoundthatifIsentstringslongerthan255characters,thestringsweretruncated.It
tookmequitesometimetofigureoutthatthewizardforVisualC++usedoutdatedfunctionality
forreceivingparameters.IneededtochangetheheaderandlibfilesfortheODSsupportthatwas
deliveredwithVisualC++6.Itdidn'thelptousethefilesfromthelatestPlatformSDK,butusing
thesamplesfromSQLServer'sowninstallationCD-ROMandtheheaderandlibfilesfromthesame
placefinallymadeit work.Inaddition,Ihadtoexportafunction(theCway)thatSQLServer7
and2000calltoseewhatversionoftheheaderfilestheextendedstoredprocedureisusing.
5
Whentheseproblemsaresolvedandtheextendedstoredprocedureisinstalledinthemaster
database,it'sverysimpletousetheextendedstoreprocedurefromyourstoredprocedures.Listing
4.10showsanexampleofhowthecentralizedstoredprocedureJnskTrace_Write() mightlook.
Listing 4.10 Example of a Centralized Stored Procedure That Makes Trace Calls
CREATE PROCEDURE JnskTrace_Write
(@traceType INT
, @source uddtSource
, @message VARCHAR(255) , @userId uddtUserId = '')
AS
DECLARE @theDbName VARCHAR(30)
SET @theDbName = DB_NAME()
EXEC master..xp_jnsktracewrite @traceType
, @source
, @message, @userId
, @@TRANCOUNT, @theDbName
Finally,tobringthewholetracingsolutionforstoredprocedurestogether,theJnskTrace_Begin()
storedprocedureisshowninListing4.11.
Listing 4.11 Code for the JnskTrace_Begin Stored Procedure
CREATE PROCEDURE JnskTrace_Begin
(@source uddtSource
, @userId uddtUserId = '')
AS
EXEC JnskTrace_Write 2, @source, '', @userId
SeveralofthedrawbacksofCOMAutomationapplytoextendedstoredprocedurestoo.Ialsothink
it'smuchhardertobuildextendedstoredproceduresthanCOMcomponents,butthatdependson
yourbackground,ofcourse.Ontheotherhand,extendedstoredprocedureshaveseveral
advantages, themostimportantofwhichisthattheyarefasterthanCOMAutomation,especially
whenseveralsmallcallsareneeded,whichisthecasewithtracing.Imeasuredasimplestored
procedurethatonlycompletedasingleupdateandhadJnskTrace_Begin() and JnskTrace_End()
tracecalls.Whenthetracecallsweredonewithmyextendedstoredprocedure,Icouldexecutethe
storedprocedurealmosttwiceasmanytimesinacertaintimeframethanwhenCOMAutomation
wasusedinstead.
Start and Stop Tracing During Production
Iuse.config filesfordeterminingwhethertracecallsshouldorshouldnotbesentfrommy
components.The.config filesarereadatcertaintimeintervalssothatreadingdoesnotbecome
thebottleneckitwouldhavebecomeifIhadreadthembeforeeachtracecallwasdone.
Atfirst,IthoughtIshouldreadfromthe.config filesinmystoredprocedurestoobecauseit
would,ofcourse,beeasiesttocontrolthetracingforallthetierswiththesamemechanism.I
couldhaveusedxp_cmdshell() forreadingfromthefiles,soitwouldn'thavebeendifficult,butit
wouldslowdownthetracing.Icouldn'tcomeupwithareallygoodcachesolutionforhowtostore
the.config valuesformystoredprocedures.Instead,Idecidedtoletthedeveloper/administrator
goindirectlyandcomment/uncommentcodeinthestoredprocedures,suchas
JnskTrace_Begin(),JnskTrace_End(),andsoon,oronlyinJnskTrace_Write() ifallcallsare
madethroughthatone.Thisisnottheperfectsolution,butistheoneIprefer.
Ikeepaninstanceofallthestoredproceduresmentionedinthissectionineverydatabase.This
way,Icanstarttracinginjustonespecificdatabaseandnothaveitstartedforallthedatabasesof
thatparticularSQLServerinstance.
Sender Filtering of Trace Calls for Stored Procedures
IfyouletJnskTrace_Begin(),andsoon,completethetracecallsdirectly,youhaveasimpleway
offiltering,suchasonlyallowingtracecallsofacertaintypetobedone.Itwillalsogiveyoua
smallperformancegainbecauseyouskipcallingtheJnskTrace_Write() procedure.Ontheother
hand,IfinditeasiertoalwaysuseJnskTrace_Write().ItmeansIonlyhaveoneplacetoaddall
senderfiltering.Thisworks,forexample,ifIaminterestedonlyinthetracecallsfromthree
specificstoredproceduresorconcernedonlyabout"Message" tracecallscontainingacertain
variable,andsoon.And,asIsaidearlier,whenyoudon'twanttohavetracingactivated,youjust
commentoutthecodeinJnskTrace_Write() andoptionallyinJnskTrace_Begin(),andsoon.
Note
Itwouldbepossibletocontroltheserver-sidefilteringforstored-proceduretracingfrom
theUserInterfaceIshowedinFigure4.2,butIhaven'timplementedthis.Ifindit
convenienttotakecareofitdirectlyandmanuallyintheJnskTrace_Write() stored
procedure.
Tracing Needs in .NET Components
Formytracingneedsin.NETcomponents,Iuseasimplersolutionthanthebuilt-inBaseClass
Library(BCL)tracingsolution.Icreatedanewclasswithinmyownnamespace.Let'stakealookat
theclass.
A New Class: Jnsk.Instrumentation.Trace
Inthissection,IproposethatyouuseJnsk.Instrumentation.Trace insteadof
System.Diagnostics.Trace.Theclassesarequitedifferent.Thefirstthingyou'll noticeisthe
selectionofmethodsinmyclass.TheonlymethodsoftheclassareTraceBegin(),TraceEnd(),
TraceMessage(),TraceError(),andTraceAssert().Theirsignaturesareallsimilarinthatthey
allrequirethefollowingparameters:
N source- Itwouldbeeasytoaddanoverloadedversionofeachmethodthatyoucanuseif
youdon'twanttogivethesourceyourself.Then,reflectionwillbeusedtodecidefrom
wherethecallwasmade.Handingoveraconstantstringismuchfaster,butless
productive.Anotherreasonforexplicitlyhandingoversource asaString isthatitdoesn't
matterifthetracemethodhasbeenwrappedwithinanothermethod.
N exeOrDllName- Justaswithsource,reflectioncouldbeusedtofindoutfromwhatEXEor
DLLthecallwas made.Onceagain,it'sfastertohandthatinformationovertothetrace
methodand,inthiscase,allthatisneededisaPublic Const forthecompleteproject.
Someofthemethods(TraceMessage() andTraceAssert())takeathirdparameter,namely:
N message- Here,thevalueofacertainvariablecanbeinspectedoranerrormessagecan
beshown.
TraceError() takesanexceptionasparameterinsteadofthemessageparameter.(Moreabout
thisinChapter9,"ErrorHandlingandConcurrencyControl.
Different Signatures
Tomaketheclassasusableaspossible,Idecidedtooverloadthemethods.Allmethodscanalso
takeoneorbothofthefollowingparameters:
N userId- Thisistheuserwhoisusingthecomponentrightnow,ifitisknownasa
parameterorviaSecurityCallContext.
N transaction- Thisindicateswhethertheinstanceisparticipatinginatransaction.
Tomakeiteasytousetracingwithservicedcomponents,Ialsocreatedthe
Jnsk.Instrumentation.ServicedTrace class,whichinheritsfromJnsk.Instrumentation.Trace.
Here,thetracemethodsgrabaContextUtil objectsothattherequiredinformation(userId and
transaction)canbetakenfromthere.
Tosummarizethedesign,IhaveprovidedtheclassdiagraminFigure4.4.Hereyoucanseethe
classesIjustdiscussed.
Figure 4.4. Class diagram for the tracing solution.
Forwarding the Call
Internally,theJnsk.Instrumentation.Trace classcouldeasilychooseamongseveraldifferent
implementations.Thetwomosttypicaltochoosefromaretocall
System.Diagnostics.Trace.WriteIf() ortodirectlymakeOutputDebugString() callsviaP-
Invoke.Theformerisidealtouseifyouonlywanttolistentoyourmanagedcode,whilethelatter
iswhatIwilldiscussmost,becauseIliketocollecttraceinformationfromdifferenttiersand
processesonthesystemandpresenttheminonelistofmessages.
Format of the String
AsIsaidearlier,Ineedastringinwhichthedifferentpartsareseparatedwithtabs.Listing4.12
showsanexampleinwhichString.Format() hasbeenusedtocreateastring.
Listing 4.12 Example of How to Use String.Format()
Dim anTmp As String = String.Format("{ 0} |{ 1} |{ 2} ", _
"Hello", "World", "Today")
Unfortunately,Ihaven'tfoundouthowtoaddvbTab tothetemplatestring(apartfromhavinga
placeholdersuchas{0}for it).InC#,\t isusedforthecontrolcharactertab.WhenIdidaquick-
and-dirtytestcomparingString.Format() andaStringBuilder objectforbuildingthetrace
message,theStringBuilder solutionwasmuchfaster.Therefore,IdecidedtouseStringBuilder
instead.StringBuilder worksverywell,andthecodeisalmostasreadableaswith
String.Format().(TherealpowerofFormat() isthecapabilitytoacceptcustomformatsand
IFormatProvider objects.)
Making an OutputDebugString() Call
SupposethatIwanttomakeanOutputDebugString() call.Theeasiestsolutionistouse
System.Diagnostics.Trace.WriteIf becausetheDefaultTraceListener willforwardthe
messageasanOutputDebugString() call.Still,IbelieveyougetbetterthroughputbyusingP-
Invoke, andIhavedecidedthisisthesolutiontogofor.Thankstocentralization,it'sverysimple
tochangeifmyguessproveswrong.(Whenversion1of.NEThasshipped,Iwillprovidea
performancecomparisonatthebook'sWebsiteatwww.samspublishing.com.)
ThereareseveralwaystocallWin32functionsviaP-Invoke.InListing4.13,youseehowIprepare
fortheOutputDebugString() callwiththemethodIprefer.It'sthemostsimilartoVB6ofallof
them.
Listing 4.13 Using P-Invoke to Call OutputDebugString()
Private Declare Ansi Sub Ods Lib "kernel32" _
Alias "OutputDebugStringA" _
(ByVal message As String)
Whenyouhavethedeclarationinplace,youjustcallOutputDebugString() likethis:
Ods("Hello world")
Making a Trace Call with My Tracing Solution
NowthatIhavepresentedmytracingsolutionatlength,I'msureyou'rewonderingwhatthecode
willlooklike.AsIsaidbefore,therearetwodifferentclassestochoosefromwhenmakingatrace
call-Jnsk.Instrumentation.Trace andJnsk.Instrumentation.ServicedTrace.ServicedTrace
inheritsfromTrace andisSystem.EnterpriseServices aware.Becauseofthis,youdon'thaveto
explicitlygiveuserId information,forexample,butitcanstillbetraced.Listing4.14showswhat
makingatracecall(of"Message" type)fromaservicedcomponentmightlooklike.Asyoucan
see,Iaminspectingthevalueofaparametercalledid.
Listing 4.14 Making a Trace Call from a Serviced Component
Jnsk.Instrumentation.ServicedTrace. _
TraceMessage(AssemblyGlobal.exeOrDllName, theSource, "id: " _
& id.ToString())
The Implementation
I'mnotgoingtoshowyouallthecodefromthetraceclasses-becauseofalltheoverloaded
methods,thereisquitealotofcode.However,I'dliketogiveyouasample.(Youcanfindallthe
codeatwww.samspublishing.com.)Listing4.15showstheTraceMessage() methodthatwascalled
inListing4.14.
Listing 4.15 An Example of a Public Trace Method (in This Case, TraceMessage())
Public Overloads Shared Sub TraceMessage _
(ByVal exeOrDllName As String, _
ByVal source As String, ByVal message As String)
Dim anUserId As String = ""
Dim anIsSecurityEnabled As Boolean = False
If EnterpriseServices.ContextUtil. _
IsSecurityEnabled Then
anUserId = EnterpriseServices. _
SecurityCallContext.CurrentCall. _
DirectCaller.AccountName.ToString()
anIsSecurityEnabled = True
End If
TraceWrite(TraceType.TraceTypeMessage, _
exeOrDllName, source, message, anUserId, _
EnterPriseServices.ContextUtil. _
IsInTransaction.ToString(), _
anIsSecurityEnabled.ToString())
End Sub
Note
YoushoulduseTry andCatch inthecodeshowninListing4.15,whileintheCatch block
youmostlikelyjustlogtheproblem.ThisisalsotrueforthecodeinListing4.16.Of
course,it'sveryimportantthatyoudon'tintroduceproblemsinyourcodewithyour
tracingsolution.
Allthedifferenttracemethods, bothinTrace andServicedTrace,willcallTraceWrite() inthe
Trace class.Listing4.16showsthecodeforthismethod.
Listing 4.16 The Protected Trace Method That All Public Trace Methods Call
Protected Shared Sub TraceWrite _
(ByVal traceType As TraceType, _
ByVal exeOrDllName As String, _
ByVal source As String, ByVal message As String, _
ByVal userId As String, _
ByVal transactionInfo As String, _
ByVal securityInfo As String)
Const aDelimiter As String = vbTab
Dim aBuffer As New Text.StringBuilder()
'Type of trace call.
Select Case traceType
Case TraceType.TraceTypeMessage
aBuffer.Append(TraceTypeMessageText)
Case TraceType.TraceTypeError
aBuffer.Append(TraceTypeErrorText)
Case TraceType.TraceTypeBegin
aBuffer.Append(TraceTypeBeginText)
Case TraceType.TraceTypeEnd
aBuffer.Append(TraceTypeEndText)
Case TraceType.TraceTypeAssert
aBuffer.Append(TraceTypeAssertText)
Case Else
Throw New Exception("Programming error!")
End Select
aBuffer.Append(aDelimiter)
'Current time.
Dim theNow As DateTime = DateTime.Now()
aBuffer.Append(theNow.ToLongTimeString)
aBuffer.Append(".")
aBuffer.Append(theNow.Millisecond)
aBuffer.Append(aDelimiter)
'Name of EXE/DLL.
aBuffer.Append(exeOrDllName)
aBuffer.Append(aDelimiter)
'Source.
aBuffer.Append(source)
aBuffer.Append(aDelimiter)
'Message.
aBuffer.Append(message)
aBuffer.Append(aDelimiter)
'ThreadId.
aBuffer.Append(AppDomain. _
GetCurrentThreadId.ToString())
aBuffer.Append(aDelimiter)
'Transaction information.
aBuffer.Append(transactionInfo)
aBuffer.Append(aDelimiter)
'Security information.
aBuffer.Append(securityInfo)
aBuffer.Append(aDelimiter)
'UserId.
aBuffer.Append(userId)
aBuffer.Append(aDelimiter)
'ComputerName.
aBuffer.Append(computerName)
aBuffer.Append(aDelimiter)
Ods(aBuffer.ToString())
End Sub
NotethatintheTraceWrite() methodshowninListing4.16,thenameofthecomputeriskeptin
thefollowingvariable:
Private Shared Readonly computerName _
As String = GetComputerName()
ThankstoShared andReadonly,theGetComputerName() methodwillbecalledasseldomas
possible.AlsoworthmentioningisthefactthatwhenIused
Process.GetCurrentProcess.MachineName.ToString() togetthenameofthemachine,onlya
dotwastheresult.Althoughthisiscorrect,itisnotwhatIwant,becauseI'dl iketohavethe
possibilitytocollecttraceinformationfromseveralmachinesinonelist,soIneedtherealmachine
names.Therefore,IcalledGetComputerNameA() inkernel32 viaP-Invokeinstead.Ilearnedlater
thatEnvironment.MachineName worksfinetoo.
Start and Stop Tracing During Production
WhenIdiscussedthebasictracingsolutionearlier,ImentionedthatIwantedtousea.config file
tostartandstoptracingduringexecution.WhenIdiscussedthetracingsolutionforstored
procedures,IalsosaidthatIhadfailedtocomeupwithagoodsolutionforusingthe.config file
becauseofperformancereasons.Whenitcomestothe.NETcomponents,Ihavesuccessfullyused
a.config file.Youhavetotakecarenottoletthereadingofthe.config filekillperformance,
suchascachingthevaluesandonlyrefreshingthematcertainintervals(eventherefreshrateis
statedinthefile).
Note
Iwilldiscussdynamicallyadjustingtovaluesina.config filelaterinthischapter.
Sender-Filtering
It'simportantnottomakeunwantedtracecalls,soeventhoughmyviewerapplicationcanfilter
tracecallsandonlyshowthosethattheuserwantstosee,it'ssometimesbettertofilteratthe
sender-side.It'sextraimportantwhenitcomestoreceivingoverthenetwork.Themaindrawback
isthattheclientcan'tgobackandbringthecallsbackagain.Thatcanbedoneforreceiver
filteringbecausethecallshavebeendoneandcaughtbutjustnotshownintheUI.Eventhe
sender-filteringsolutiondependsontheconfigurationfileandthetricksImentionedinthe
previoussection.
VB6 Components, Windows Forms, ASP, and ASP.NET
ThecompletetracingsolutionusingOutputDebugString() worksperfectlywellforVB6
componentstoo.Icouldreusemy.NETtracecomponent,buttoskiptheoverheadof
interoperability,ImakethecalldirectlyfromaVB6codemoduleinstead.(Seethebook'sWebsite,
www.samspublishing.com,forthecode.)
AlthoughIamfocusingonthedataandmiddletiersinthisbook,thereisnothingtopreventyou
fromusingthetracingsolutionfortheConsumertier.Infact,Iactuallyrecommendyoudo,sothat
youcangatheralltherelevantinformationinonetracelist.Onceagain,thecentralizationshines
becauseyoucaneasilyshifttoASP.NETbuilt-intracingifyouonlywanttotracetheASP.NETcode.
Checking the Solution Against the Requirements
Thereisdefinitelyaperformancepenaltywithtracing.Whennooneislistening,it'squitelow,and
whenit'salsodeactivated,it'sverylow,whichisjustthewayIwantit.Icanacceptthatthe
performancepenaltyishighontherareoccasionswhenIamlistening.WhenIlisten,thereisa
goodreason,andifthethroughputsuffersabitforafewminutes,thatistypicallylessofa
problemthantheoneI'mtryingtosolve.Theimportantthingtorememberistousetracecallsin
asmartway.Watchthatyoudon'tputtheminatightloop,forexample.Intheevaluationsection
attheendofthischapter,wewilldiscussperformanceindetail.Tobehonest,myproposed
solutionforremotereceivingisprimitiveandwillprobablyonlyworkinsomescenarios.Ifyou
needtouseremotereceiving,Iwouldconsiderinvestigatingathird-partysolutioninstead.
Inaddition,thestoredproceduresolutionisnotreallyusablefromuser-definedfunctions(UDFs).
InthecurrentimplementationofUDFsinSQLServer2000,youcan'tcallstoredprocedures.
However,youcancallextendedstoredprocedures,soit'spossibletousemytracesolution,but
yourunintotroublewiththeconfigurationaspects.Icouldcreatetwoversionsofthe
xp_jnsktracewrite():oneemptyandonethatdoesthetracecall.It'sthenpossibletoregister
thecorrectone,dependingonwhethertracingshouldbeactivated,butnowthingsarestartingto
getmessy.Therearejusttoomanyplacestochangebeforetracingcanbestartedand,because
theextendedstoredprocedureisforthewholesystem,itwon'tbepossibletodecidetousetracing
forsomedatabasesandnottouseitforothers.Ontheotherhand,itwouldn'tbesuchagoodidea
tosendtracecallsinascalarUDF.Imaginehavingitoperateonallrowsinaresultsetwith10,000
rows,forexample.
There'salotmoretosayaboutthetool.Ifyouarecurious,takealookatthecodedownloadon
thebook'sWebsite,butrememberthatitisnotacommercialproductbutratheraquickhack,so
useitforinspirationpurposesonly.
Error Logging
Letmetellyouastorythathappensofteninmyprofessionallifeandthatyou'resuretorecognize
yourself.ThestorystartswhenIgotoseeacustomertodiscussanewproject:
"HiJimmy!Nicetoseeyou.
"HiJohnDoe!Nicetoseeyoutoo.
"Beforewegetstarted,oneoftheusershadaproblemlastweekwiththeotherapplication.
"Oh,that'snotgood.Whatwastheproblem?
"Hmm.,Idon'tremember.IthinkitwasSarah,orperhapsitwasTed..Therewasanerror
messagesayingsomethingaboutaproblem.
Whatdoyoudointhissituation?Sure,youcanaskthemtowritedownthespecificerrormessage
indetaileverytime.Butitisn'ttheirresponsibilitytologinformationabouterrors-it'syours.The
storyendswithyousaying,"Noproblem,I'lltakeaquicklookintheerrorlogandseewhatthe
problemwasandprobablywhatcausedittoo.(Ilovehappyendings.)
Note
Afriendofminehastrainedhisclientsverywell.Once,oneofhisclientsgavehimallthe
informationfoundonscreenattheeventofa"blue-screen,whichwastwohandwritten
pagesfullofhexcodes.Hisclientdidwhathehadbeentoldtodo.
Tracingisgoodwhenyoulisten.Loggingisgoodevenwhenyouhaven'tlistened,afteraproblem
hasoccurred,forexample.Idon'tthinkofloggingasseeingtheexecutionpaths,butratheras
storinginformationaboutexceptionalsituations,suchaserrorsandbrokenassertions.IfIwantto
seetheexecutionpath,Iusethetracingsolutioninsteadandsetitinfilemode,whichmeans
writingreceivedtracecallstoafile.
Inthissection,I'mgoingtoshowyouaproposalforhowtotakecareoftheloggingforboththe
storedproceduresandthecomponents.Butbeforedoingthat,letmebrieflydescribehowthe
built-inloggingsolutionin.NETworks.
Note
AlthoughinthissectionIdiscussloggingonlyasitreferstoerrorlogging,youcanextend
thesolutionsothatitappliestologgingingeneral(tocollectstatistics,forexample).
The Built-In Logging Solution in .NET
Thebuilt-insolutionforloggingin.NETisactuallyanothersubclassoftheTraceListener class,
calledEventLogTraceListener.Itcanbeusedtolistenforfailedassertionsandtracederrors.
My Proposal for a Logging Solution
InearlierloggingsolutionsIhaveused,Iwroteinformationaboutproblemstotheeventlog.I
thoughtaboutusingthedatabase,butmyargumentfornotdoingsowasthattheproblemI
wantedtologcouldbethatthedatabaseserverwasn'tupandrunning.Thetwomainproblems
withwritingtoeventlogsare
N You don'thaveacentralizedlog.Onepartofthetotallogcouldbestoredintheeventlogat
theSQLServer,onepartattheapplicationserver,andthelastpartspreadoverallthe
workstations.
N Runningstatisticsanddrawingconclusionsfromtheproblemsisnot"atyourfingertips.
Let'sfaceit,weusedatabasesforsolvingbusinessproblemsforourcustomers.Whynot
usethisgreattechniqueforourownbusinessproblems?InSweden,wehaveasayingthat
goes,"Theshoemaker'schildhastheworstshoes.Wouldyoutrytoconvinceyour
customerthatusingtheeventlogisagreatsolutionforstoringhisbusinessdata?Hopefully
not.
I'dwantmyloggingsolutiontouseamoreconvenientstoragevehiclethantheeventlog.Ialso
wantmysolutionto"forcethedevelopertoprovideastandardizedsetofinformation.The
informationisdifferentdependingonthetier,butthemorerelevantinformation,thebetter.In
addition,Ifindloggingisdifferentfromtracecallsbecauseitshouldbepossibletoli stentotrace
calls,butnotobligatory.Itshouldevenbepossiblenottohavetheminthereleaseversion.Itis
justtheoppositewithlogging.Loggingisobligatoryandshouldalwaysbeused.Nobodywilllisten
tologcallsinrealtime;they'llanalyzethemafterward.
Becauseoftheproblemsdiscussedwithboththebuilt-inloggingin.NETandmyearlierattempts,I
havedesignedasmallsolutionofmyownthatIfindbettersuitedtomyneeds.Althoughthe
problemcouldcertainlybethatthedatabaseserverisdown,inreality,mostoftenthedatabase
serverisworkingjustfine,weekafterweek,monthaftermonth.Nowadays,Iuseadatabasetable
formyerrorlogging.Itwillconsumesomeresourcesanditwilltakesometimetologaproblem,
but,ontheotherhand,you'reusuallynotinahurrywhenyouhaveanunexpectederror.Youthen
haveamorefundamentalproblem.
Ialsoliketheideaofusinganordinaryfileinthefilesystemforthelogging.Thefilesystemwould
probablyhaveproblemsevenlessoftenthanthedatabase.It'salsohighlyefficientinNTand
Windows2000towritetoafile.(Ontheotherhand,writingtoashareisnotthatfast,and
rememberthattherewillbeseveralprocessesandthreadsthatwillwritetothesamefile,whi ch
willleadtoserializationeffects.)Anotheradvantageisthatwhenthedeveloperwantsalog,the
localadministratorjusthastoattachthelogtoanemail(ifthedeveloperdoesn'thavedirect
access).Anyway,personallyIpreferthedatabasesolution.
Note
ApowerfulsolutionistologontoUnicenterTNG,MicrosoftOperationsManager2000
(MOM),Tivoli,orasimilaradministrationtool,ifyourorganization/customerusesoneof
these.(ManyoftheadministrationtoolssupportWindowsManagementInstrumentation,
WMI.)Youcanalsouseanagentfortheproductathandthatcansnifftheeventlog,for
example.Thatway,youdon'thavetomakespecificcallsinyourcode.
Iusuallypopanerroronelayerup(orjustleaveit)tothecalleeuntilIreachtherootmethod.I
havetriedtohavetheproblemloggedineachlayer,butIcouldn'tfindagoodsolutiontothat
whenstoredprocedurescallstoredprocedures.ThereareproblemswithT-SQLerrorhandling,and
thebestwaytoescapethemistouseRETURN() forreturningtheresulttoacallingstored
procedureandRAISERROR() forreturninginformationfromeachstoredprocedurebacktoa.NET
componentthatcantakecareofthelogging.
Note
YouwillfindalotmoreinformationabouterrorhandlinginChapter9.
Let'stakealookattheinfrastructurefortheloggingsolution.Thedatabasetableforthelogging
solutionisshowninFigure4.5.
Figure 4.5. Database table for the logging solution.
Note
Before.NET,IusedanINT forerrorcode;nowadays,IuseaVARCHAR insteadsothatI
canwritethenameoftheexceptioninsteadofaHRESULT.
Inthedatabasetable,youcanalsoseethatIdecidednottoprovideacolumnforeverypossible
pieceofinformation.Instead,Idecidedtogroupmostoftheinformationtogetherandwritei tto
theotherinformation column,typicallyasanXMLdocument.ThatiswhereIstoreinformation
abouttheprocessID,iftherewasanactivetransaction,andsoon.Infact,Istoreeverythingthat
couldproveusefulwhenItrytounderstandtheproblem. Still,Iuseseparatecolumnsforthemost
basicinformation,suchaserrorcode forthenameoftheexception,description asusuallyused,
source forthenameofthemethodorstoredprocedure,userId asusuallyused,and
errordatetime provideswhentheproblemoccurred.
YoualsoseethatthereisacolumncallederrorstackandthatitisalsoinXMLformat.Thelogitem
istypicallywrittenfromthe"firstlayerof.NETcomponents,whichIcallthe"Applicationlayer.
(StenSundbladandPerSundblad
6
callitFacade,andTimEwald
7
callsitProcess,tomentionafew
alternatives.)There,thecompleteerrorstackwillbeloggedtogetherwithwhatwastheerrorcode
anddescription ineverylayerandmethodwheretheerrorwasraised.Finally,forthetimes
whenthereactuallyisaproblemwithloggingtheerrortothedatabase,Iwillresorttowritingto
theeventlog.
Stored Procedures for the Logging Solution and Logging Errors in Stored
Procedures
BecauseIstoretheerrorinformationinatableinthedatabase,storedproceduresareusedfor
takingcareofthewriting.Iwilldiscussthesestoredproceduresinthissectionandhowtheyare
usedfromcomponentslaterinthechapter.
General Stored Procedures
Theactualworkofwritingtothejnskerror_errorlog tableistakencareofbyagroupofstored
procedures.Atfirst,thisseemsstraightforward,butthesolutionmustbeabletohandleasituation
inwhichanerrorthatmustbeloggedleadstorollbackofthetransaction.Mostoftenwethinkof
transactionsasourfriends,butinthecontextoflogginganerror,theyactuallycreateaproblem
forus.Assumethatthecomponentthatwilltakecareoftheloggingistransactionalandthata
problemneedstobewrittentothelogtable.Unfortunately,loggedrowswillgetrolledbackwhen
COM+decidestogoforROLLBACK insteadofforCOMMIT.Forservicedcomponents,wehavea
specifictransactionattributetouseinthissituation,namelyRequiresNew transaction,butIthink
it'soverkilltocreateadistributedtransactionjustbecauseyouarewritinganitemtotheerrorlog.
Wecouldalsohaveanouterobjectthatisnottransactionaltotakecareofthelogging.Thiswould
alsosolvetheproblem,butthenwehavecreatedasolutionwherewecan'tchangethe
transactionalsettingsfortheouterobjectwithoutseverelyaffectingtheapplication.(InChapter6,
"Transactions,IdiscusswhyIwanttobeabletoredeclaretheattributevaluesfortransactions.)
Yetanothersolutionistotakecareoftheloggingfromacomponentthatismarkedwith
NotSupported transactions.Thatway,theworkofthecomponentwillneverhappeninsideaCOM+
transaction.Thedrawbackwiththissolutionisthatyourcomponentmustbelocatedinacontext
objectofitsown.(IwilldiscusslocationofobjectsincontextobjectsalotinChapter5.)Thebest
solutionIhavecomeupwithistousexp_cmdshell tocallosql,which,inturn,willexecutethe
storedprocedurethatwillwritetothejnskerror_errorlog table.Byusingthistrick,thelogging
willruninitsowntransaction,andtheproblemissolved.Thesolutionisabitofhack,butitworks
fine.Tospeedthingsupabit,IonlyusethissolutionwhenIfindanactivetransactionatlogging
time;otherwise,Iskip thisextraoverheadandjustcallthestoredprocedureforloggingdirectly.
InListing4.17,youcanseethecodethatdecideshowtowritetothejnskerror_errorlog table.
Asyousee,ifthereisanactivetransaction,astoredprocedureiscalled;itwillusexp_cmdshell
asIdescribedearlier.Ifthereisn'tanactivetransaction,anotherstoredprocedurewillbecalled
thatwillwritetothejnskerror_errorlog tabledirectly.
Listing 4.17 The Stored Procedure That Is the Entry Point for Logging
CREATE PROCEDURE JnskError_Log (@source uddtSource
, @error VARCHAR(255), @description VARCHAR(255)
, @errorDateTime DATETIME
, @otherInformation VARCHAR(1000)
, @errorStack VARCHAR(6000)
, @userId uddtUserId)
AS
SET NOCOUNT ON
IF @@TRANCOUNT > 0 BEGIN
EXEC JnskError_LogWriteInNewTransaction
@source, @error
, @description, @errorDateTime, @otherInformation
, @errorStack, @userId
END
ELSE BEGIN
EXEC JnskError_LogWrite @source, @error
, @description, @errorDateTime, @otherInformation
, @errorStack, @userId
END
Figure4.6showsaninteractiondiagramforthedifferentcallstacksused,dependingonwhether
thereisanactivetransaction.
Figure 4.6. Interaction diagram for the different call stacks.
Forcompletenessandtomakeiteasiertounderstandtheconcept,Ihaveshownthe
JnskError_LogWriteInNewTransaction() inListing4.18andJnskError_LogWrite() inListing
4.19.(PleasenotethatIskippedtoshowtheerror-trappingcodehere.)
Listing 4.18 The Stored Procedure That Creates a New Transaction Before Logging
CREATE PROCEDURE JnskError_LogWriteInNewTransaction
(@source uddtSource
, @error VARCHAR(255), @description VARCHAR(255)
, @errorDateTime DATETIME
, @otherInformation VARCHAR(1000)
, @errorStack VARCHAR(6000)
, @userId uddtUserId)
AS
DECLARE @aTmp VARCHAR(8000)
, @aReturnValue INT
SET NOCOUNT ON
SET @aTmp = 'osql -E -d' + DB_NAME() + ' -Q"JnskError_LogWrite '
+ '''' + @source + ''',''' + @error + ''''
+ ',''' + @description + ''','''
+ CONVERT(VARCHAR(30),
@errorDateTime,113) + ''''
+ ',''' + @otherInformation + ''','''
+ @errorStack + ''',''' + @userId + ''''
+ '"'
EXEC @aReturnValue = master..xp_cmdshell @aTmp
Listing 4.19 The Stored Procedure That Writes to the Table
CREATE PROCEDURE JnskError_LogWrite (@source uddtSource
, @error VARCHAR(255), @description VARCHAR(255)
, @errorDateTime DATETIME
, @otherInformation VARCHAR(1000)
, @errorStack VARCHAR(6000)
, @userId uddtUserId)
AS
DECLARE @anError INT
, @aRowCount INT
, @aTmp VARCHAR(8000)
SET NOCOUNT ON
INSERT INTO jnskerror_errorlog
(source, errorcode, description, errordatetime
, otherinformation, errorstack, userid)
VALUES
(@source, @error, @description, @errorDateTime
, @otherInformation, @errorStack, @userId)
Logging an Error in a Stored Procedure
Nowthatyouhavetheerror-loggingstoredproceduresinplace,howcanyouusethemfromyour
ordinarystoredprocedures?Onceagain,becauseofproblemswitherrorhandlinginT-SQL,Ionly
logproblemsthatarereturningtothecomponents.Otherwise,Iwouldhaveseverallogrowsfor
everyproblem.
Note
Italkmoreabouttheerror-handlingtipsandtricksinChapter9,"ErrorHandlingand
ConcurrencyControl.
Logging an Error in a .NET Component
Myproposalfortakingcareofloggingin.NETcomponentsisprettystraightforward.Inthissection,
I'mgoingtoshowyouwhatthesolutionlookslikeandhowitcanbeusedfromyour.NET
components.
Creating the Error Stack
Withthehelpofreflectionin.NET,it'seasytogettheerrorstackfromthecomponentsgenerated.
Actually,usingtheToString() methodofanexceptionwillgiveyouthatinformation,butyoucan
alsoiteratetheerrorstackmanuallytograbtheinformationyouwantandformatitthewayyou
prefer.
Note
Forthistowork,theexceptionsneedtobechainedwiththehelpofinnerException.I
willdiscussthisfurtherinChapter9.
The Log() Method Itself
InListing 4.20,youcanseeoneoftheLog() methodsinJnsk.Instrumentation.Err.Itgathers
alltheinformationandthensendsitovertoLogWrite(),whichwillsavetheinformationtothe
database.
Note
Onceagain,Try andCatch areleftoutinthecodeinListing4.20,butthisisdone
intentionally.Iwilldiscussthisissueagreatdealinthefollowingchapters.Thatsaid,you
definitelyshoulduseexceptionhandlinginthecodeinListing4.20.Forsomeerrors,you
willlogtotheeventlogonly,andforsome errors,youwillprobablydonothingandjust
"eattheexception.
Listing 4.20 The Log() Method That Extracts All the Information to Log, and Then Calls
the LogWrite() for Saving to the Database
Private Shared Sub Log(ByVal exeOrDllName As String, _
ByVal source As String, _
ByVal anException As Exception, _
ByVal userId As String)
Dim anExceptionName As String = _
anException.GetType.ToString
Dim aDescription As String = _
anException.Message
Dim anErrorDateTime As DateTime = DateTime.Now
'Build the ErrorStack.
Dim anErrorStack As String = _
BuildErrorStack(anException)
'What other information to grab?
'Only exeOrDllName for now.
'Extend later on.
Dim anOtherInformation As String = _
BuildOtherInformation(exeOrDllName)
'Write to the database.
LogWrite(source, anExceptionName, _
anDescription, anErrorDateTime, _
anErrorStack, anOtherInformation, userId)
End Sub
Note
AtypicalextensiontothecodeinListing4.20couldbetoaddEnvironment.StackTrace,
tonotonlycollecttheStackTrace fromtheException butalsothecompletecallstack.
Jnsk.Instrumentation.ServicedErr inheritsfromJnsk.Instrumentation.Err togetthesame
advantagesasfortheTrace andServicedTrace classes.AsyousawinListing4.20,Ididn'tshow
theservicedversionthistime,butintheclassdiagraminFigure4.7,youcanseeboththeclasses
withalltheiroverloadedmethods.
Figure 4.7. Class diagram showing the Err and the ServicedErr classes.
Call to the Log() Method: Hidden in Another Method
IcentralizemycodeforraisingerrorsinamethodcalledJnsk.Instrumentation.Err.Raise()
(andJnsk.Instrumentation.ServicedErr.Raise()).Here,ImakemyerrortracecallandIalso
logtheproblem,butIwon'talwayslogproblemswiththeRaise() method.Iwilldiscussthisin
moredetailinthenextsection.Notethatitcouldbeinterestingtobeabletoconfi gurewhattypes
oferrorstolog,andhowunexpectederrorsarealwayslogged,butbrokenbusinessrulesarenot.I
haven'timplementedsuchasolutionhere,buttheproposedsolutioncaneasilybeextendedto
supportthisfunctionality.
Note
Throw() isn't recommendedtobemovedtoahelper.Evenso,Irecommendyouusea
helperwhenraisingexceptions.Skiptheflaminge-mailsuntilyouhavereadaboutmy
solutioninChapter9.
Reflection, Interception, and Attributes to Your Service
It'stedioustoaddspecificcodesnippetsoverandoveragaintoeverymethod.Itwillalsogiveyou
codethatishardertoreadbecauseyou"won'tseetheforestforthetreesduetoallthenoise.
Becauseofthis,IcreatedageneratorbackintheVB6daysthatmadeacopyofallthesourcecode
andtheninsertedcodesnippetsatstandardizedplacesinthecopy.Forexample,itaddedcallsto
TraceBegin() andTraceEnd() toallmymethods.Whenitwastimetoworkonthecodeagain,I
wentbacktotheoriginalsourcecodewithoutalltheroutine-usedtracecalls.WhenIstartedto
playwith.NET,itwasmyideathatIcouldcombineattributesandreflectiontohavethecalls
generatedforme.Inthefollowingsections,Iwilldiscussseveralwaysyoucanbenefitfrom
attributesandreflectionin.NET.
Custom Attributes Helping to Generate Code
WhenIstartedtothinkaboutcustomattributesin.NET,Ithoughttheywouldcometotherescue
andcreatecodeforme.Unfortunately,I'mnotthecompilerandthereforeIdon'thavethat
opportunity.WhatIcandoistoaskifacertainattributeissetandusethatinanordinaryIf
clause.However,thiswon'tsavemeanycode,itwillonlyaddtoit.Itwillalsocostquiteabitof
runtimeoverhead.
Apossiblesolutiontotheproblemcouldbetouseinterception.Ifyourcomponentinheritsfrom
ContextBoundObject,thatcouldbeusable.
8
Inthecaseofservicedcomponents,thisisjustthe
situationbecauseServicedComponent inheritsfromContextBoundObject.Thenit'salsopossibleto
investigatetheactualvaluesoftheparameters.Atfirst,Ithoughtautomaticallycachingthe
parametervaluesformytracingsolutionwasaverypromisingsolution,butIpreferamore
manualsolutionwhereIdecidewhatparametervaluestoshow.(WhatshouldIotherwisedowith
morecomplexparameters suchasDataSets,forexample?)
Note
It'spossible,andeasy,totraceparametervaluesforstoredprocedurescalledfrom
components.You'llseehowinChapter8,"DataAccess.
IalsopreferamoregeneralsolutionthatIcanuseforallcomponents,nomatterfromwherethey
areinherited.IfIuseraw-configuredcomponents(discussedinChapter5),Iwillalsobeunlucky
withinterceptionbecauseinstancesoftheraw-configuredcomponentswillsharethecontextofthe
firstcomponentandhavenointerceptionoftheir own.Finally,thissolutionconsumesalotof
resourcestoo.
AnothersolutionwouldbetousetheICorProfilerCallback interfaceandcreateaprofilerthat
willbenotifiedwhenamethodisenteredandexited.Thislooksveryexciting,butbuildinga
profilerjustforaddingtracecallsseemtobeoverkillforme,andIhaven'tinvestigateditfurther.
Therefore,IdecidedtostaywiththesolutionofmanuallyaddingaTraceBegin() andTraceEnd()
tomymethods.It'snotthatbigaproblembecauseItrytostandardizemycodestructureanduse
templatesanyway.(We'llexaminestandardizedcodestructuresmoreinChapter5.)
Still Benefiting from Custom Attributes
So,istherenowaytobenefitfromcustomattributes?Therecanonlybeoneanswertothat
question,can'tthere?Ofcoursetherearesituationswhenwecanbenefitalotfromcustom
attributes.OneobvioussituationiswhenI onlywanttologerrorsinmyentrymethodsinthefirst
layerof.NETcomponents.Inthiscase,Icoulddefine,forexample,
Jnsk.Instrumentation.EntryMethodAttribute forthosemethods.Inmyloggingmethod,Icould
investigateifthatattributewasdefinedinthecallingmethodandthenIwouldknowwhetherI
shouldmakealogcall.
Anotherpossiblesolutionwouldbetoimplementanemptyinterfaceandusethatasamarker.This
isnotMicrosoft'srecommendedapproach,butinperformance-sensitivecode,itcouldbepreferred.
Evenfaster,andperhapsthemostobvioussolutionistoaddaparametertotheRaise() method
thatsayswhethertheerrorshouldbelogged.ThatisthesolutionIamusing.Notbecauseof
efficiency,becausethatisnotthatbigaproblemforerrorlogging,butbecauseofsimplicity.
Putting Reflection to Good Use Once Again
Beforethedaysof.NET,Isometimeswrotemethodsthatcouldgivethedescriptionforthevalue
ofanenumeratorvariableinsteadofjustthenumericvalue.Thatwasveryhelpfulinsome
debuggingsituations.Nowadays,wedon'thavetodothat.Instead,wecanjustusereflectionto
describethevalues.
SupposethatyouhaveanenumeratorthatlooksliketheoneinListing4.21,wheresomepossible
aspectsofaprogrammerarelisted.
Listing 4.21 An Enum with Possible Aspects of Programmers
Public Enum ProgrammerAspect
Eager
Geeky
TopLevel
Lazy
End Enum
IfyouwanttotracenotonlythevalueofanEnum variablecalledaProgrammerAspect,butrather
thedescription,thecodeshowninListing4.22canbeused.
Listing 4.22 Get the Description of the Values of an Enum Variable
aProgrammerAspect.Format(aProgrammerAspect.GetType, _
aProgrammerAspect, "G")
TheresultofListing4.22wouldthenbe,forinstance,"Geeky.Wewouldprobablygetamore
usefulEnum inthiscasebyaddingtheFlags() attribute,becauseaprogrammerwillmostoften
havemorethanoneofthegivenaspects.InListing4.23,thesameEnum asinListing4.21is
shown,butthistimewiththeFlags() attributeset.
Listing 4.23 An Enum, Where the Flags() Attribute Has Been Set
<Flags()> Public Enum ProgrammerAspect
Eager = 1
Geeky = 2
TopLevel = 4
Lazy = 8
End Enum
Now,theresultofListing4.22could,forexample,beEager, Geeky, TopLevel becausethe
variablethistimehasthefirstthreebitsset.
The Conditional Attribute
YetanotherwaytobenefitfromattributesistouseConditional attributes.EventhoughIprefer
toleavemytracecallsinthereleaseversion,therearesituationswhenIdon't.Asusual,youcould
useconditionalcompilationargumentsaroundthebodyofthetracemethods,butthenthecallsto
thetracemethodswillstillbedone.IfthetracemethodsaredecoratedwiththeConditional
attribute, thecallswillnotbeintheexecutable,either.InListing4.24,youcanseeanexampleof
asubthathastheConditional attributeset.
Listing 4.24 A Method with the Conditional Attribute
<Conditional("MyCustomConstant")> _
Public Sub MySub(ByVal message1 As String, ByVal message2 As String)
TheConditional attributedoesn'tautomaticallydeletethecodeforpreparingthecall,because
buildingstringsandsuchwillnotbenefitfromthissolution,unlessthebuildingisdoneinlineinthe
callitself.InListing4.25,thecalltoMySub() won'ttakeplaceifMyCustomConstant hasn'tbeenset
toTrue.ThesamegoesforthecalltoBuildAnotherString().ThecalltoBuildTheString() will
executeinbothcases.
Listing 4.25 The Call Itself Will Also Be Deleted from the Intermediate Language (IL)
Dim aTmp As String = BuildTheString()
MySub(aTmp, BuildAnotherString())
Attributes and Reflection in Stored Procedures
Asusual,thesophisticationlevelsfor.NETandT-SQLarelightyearsapart,soitisnotpossibleto
useanattributetechniqueforstoredprocedures.Ontheotherhand,youcanscriptallthestored
proceduresandthenrunasimilargeneratortotheoneIusedformyVB6componentsonthe
script;Imeantoinsertthecodeatcertainplaces.
Inormally skipthatandworkonmystoredprocedureswithalltheroutinecodeincluded.Atleast
toolssimilartothetemplatefunctionalityinSQLQueryAnalyzerforSQLServer2000makesita
no-tasktowritetheroutinecode.
Configuration Data
Eventhoughthis isachapterabouthowtoadddebuggingsupport,Iwouldliketodiscussinmore
detailhowyoucandealwithconfigurationdata.Ihopenoneofmyreadersarethe"Idon'tcreate
anybugs,soIdon'thavetoreadaboutdebuggingtypesandhavethereforeskippedthischapter.
Asweallknow,techniquesfordealingwithconfigurationdataareclearlyusefulinmanysituations,
notonlyforaddingdebuggingsupport.
WhenIrefertoconfigurationdata,I'mtalkingaboutthekindofconfigurationdatathatyouneed
torefreshon-the-fly(suchasenabling/disablingtracing),aswellasmorestaticconfigurationdata,
suchasconnectionstrings.Beforewelookathowtodealwithconfigurationdata,I'dliketosaya
fewmorewordsaboutthedataitself.
Note
It'snotjustpossibletoenable/disabletracingbychangingconfigurationdata.Itisalso
easy,forexample,toswitchbetweendifferenttracingimplementationstobeusedby
changingthecontentoftheconfigurationdata.
Different Types of Configuration Data
Thefollowingarethedifferenttypesofconfigurationdata:
N Fetchedonce,usedmanytimes
N Fetchedeverynowandthen,usedmanytimes
N Fetchedeachtimeitisused
Ijustmentionedconnectionstrings,andthiskindofconfigurationdataisatypicalexampleof
"fetchedonce,usedmanytimes.Whenyourserverapplicationisrunning,it'sperfectlyfinetosay
thatfortheapplicationtorefreshtheconnectionstringtoworkwithanotherdatabaseserver
insteadofthecurrentone,youhavetoshutdowntheserverapplicationsothatthenextrequest
willleadtotheconnectionstringbeingreadagain(andthenewvalueisthenfound).
Thesecondcategory,"Fetchedeverynowandthen,usedmanytimes,canbeexemplifiedwith
settingsforconfiguringtracing.Youdon'tusuallywanttoshutdowntheserverapplicationsimply
tochangethefilterfortracingortoactivatetracing.Instead,readingtheconfigurationdatafrom
persistentstorageisoftengoodenough,say,onceaminute,andallrequestsfortheconfiguration
dataareservicedwithcacheddatainthistime.
Finally,thethirdcategory,"Fetchedeachtimeitisused,isnotactuallyacommontypeof
configurationdata(atleastIcan'tthinkofanexample).Ifyouencounterthissituation,itmightbe
agoodideatothinktwiceaboutwhetherthisisreallyconfigurationdataorsessiondata,user
data,orapplicationdata.Suchdatathatmustbefetchedfrompersistentstorageeachtimeitwill
beusedoftenenjoyslivinginthedatabase.
There areseveraltechniquestochoosefromwhendealingwiththesedifferenttypesofdata.
However,I'dliketostartwithashortdiscussionabouthowIsolvedtheproblemofdealingwith
configurationdataintheolddaysofVB6.
Dealing with Configuration Data in VB6
WhenwewroteCOM+componentsinVB6,theywereSingle-ThreadedApartment(STA)
components.ThismeantthatinonespecificSTA,onlyonemethodcouldrunatthesametime.To
getmultitasking,severalSTAswereavailableatthesametimeforaprocess.Inthecaseofthe
COM+serverapplication,therewereapproximatelytenSTAsmultipliedbythenumberof
processorsinthemachine.Assumethatyouwereusingamachinewithtwoprocessors;your
COM+serverapplicationwouldthenhavehadapproximately20threads.Eachthreadwouldhave
haditsownpublicdata-thatis,globalvariables,module-globalvariables,andstaticvariables(but
notmemberdata).ThepublicdatawasstoredinWin32'sThreadLocalStorage(TLS).
Thisimplementationcouldbeamajorprobleminsomesituations.Developerswhodidn'tknowthat
globalvariableswerelocaltothreadsandsharedbetweenuserscreatedhorrible,hardtofind
bugs,becauseseveralusersweresharingtheglobaldata.Yettheglobaldatawasn'tcompl etely
shared;therewereseveral(say,20)instancesofit.
Ontheotherhand,globaldataperthreadcouldsometimesalsobeagreatbenefit.Eventhoughit
wasanimplementationdetailthatcouldchange"anytime,Iactuallybasedsomesolutionsonthis
verysituation.Thetypicalexamplewaspurelyconfigurationdata.Whenthefirstrequestcamein
toanewlystartedCOM+serverapplication,itwaspossibleaconnectionstringwasneeded.Thena
globalvariablewasinvestigatedtoseewhetherithadbeeninitialized.Ifnot,theconnectionstring
wasread-forexample,fromtheRegistry-andthevaluewasputintheglobalvariable.Thenext
timearequestforthatverythreadmadethesameinvestigation,theconnectionstringwasfound
andtherewasnoneedtoreadtheRegistryagain.Thenicethingwiththesolutionwasthatit
neverintroducedanywaitstates,becauseeachthreadhaditsowndataanddidn'thavetowaitfor
anyotherthread,whichwasveryefficient.(Themaindrawbackwasthatyouhadthedataloaded
inseveralinstancesinmemory,whichcouldbeaprobleminasituationwhereyouwantedto
refreshthedataforallthethreadsatthesametime,withoutdoingashutdownoftheCOM+
application,orifthedatawaslarge.Ifso,youwastedmemory.)
WhenyouwriteCOM+componentsin.NET,yougetfree-threadedcomponents,butthereisa
techniquetogetasimilarsolutiontothatinVB6.BeforeIstartthediscussionabouthowtodeal
withthisin.NET,I'dquicklyliketopointouttwoimportantdesigndecisions-thedesign
perspectiveandcentralization.
The Design Perspective
First,rememberthatinthisbookwearediscussingwritingservicedcomponents,anditisfromthis
perspectivethatIneedtheabilitytorefreshthevalueson-the-fly.ForanEXE,thisisnotusuallya
problem;it'sjustamatterofrestartingtheEXE.However,restartingaCOM+serverapplication
willoftenaffectmanyusers.
Note
InChapter9,IpointoutthatyoushouldbepreparedfortheproblemofwhentheCOM+
applicationisshutdownanddiscusshow toprepareforit.
BecauseI'mtalkingaboutservicedcomponents,thisalsomeansthatIhavemoreoptionsforhow
todealwiththeconfigurationdata.I'lldiscussthismorelaterinthechapter.
Centralization
YouprobablyknowbynowthatI'mveryfondofcentralizingcode.Ifeelthesamewhenitcomes
toaccessingtheconfigurationdata.Whenamethodneedsconfigurationdata,themethodshould
justcallanothermethod,probablyaShared one,tofindtheneededdata.Themechanismthatis
usedshouldbecompletelytransparenttothecallingmethod.
Nowthatwe'velookedatthesetwoimportantdesigndecisions,let'sgetdowntothenittygritty.
You'llseethatIhavedividedthesolutionoptionsI'mabouttodiscussintotwoparts.Thefirstpart
discusseshowtostoretheconfigurationdatapersistently,andthesecondpartdiscusseshowto
accesstheconfigurationdatafromtransientstorage.I'llstartwiththefirstpart-persistent
storage.
Persistent Storage
Therearemanytechniquesfromwhichtochoose.Thefollowingisalistofanumberofthem,
althoughthelistisnotexhaustive:
N Usingtheusualdatabase
N UsingtheRegistry
N UsingActiveDirectory
N UsingObjectConstruction(OC)
N Usinga.config file
Let'sdiscusseachofthesetechniquesalittlefurther.Whenyoureadthisdiscussion,pleasekeep
inmindthatit'snotmyintentionthatthe persistentstorageshouldbehiteachtimethe
configurationdataisneeded,butratheritshouldonlybehitonceoratleastinfrequently.(The
secondpartofthesolution,howtoaccesstheconfigurationdataintransientstorage,willfurther
explainthis.)
Using the Usual Database
Thefirsttechnique,usingtheusualdatabaseforholdingconfigurationdata,mightseemabit
strange,butthisisoftenareallygoodidea.Thegoodthingaboutitisthatyouhaveone
centralizedplaceforthestoredconfigurationdata,evenifyouhaveaclusterofapplicationservers.
Youalsohavetheprotectionoftransactionsifthatisarequirementforyouinthiscase.
Onedrawbackcouldbethatyouwastesomescarceresourceswhenyouhavetohitthedatabase
forfetchingconfigurationdatatoo.Thisisnotusuallyaproblem,becauseyouwillrarelyhit
persistentstorageforconfigurationdata,butit'sstillsomethingtothinkabout.
Iknow,Iknow.Storingconnectionstringsinthedatabaseisnot agoodsolution,atleastnotifthe
connectionstringsdescribehowtoreachthedatabase.
Using the Registry
IhavetoconfessthatIused.ini fileslongaftertheRegistrywasintroduced,butafterawhileI
startedtolikethesolutionthattheRegistryrepresents. (Sure,Iseetheproblemswithit,too,butI
stilllikeit.)Asyouknow,MicrosofthasnowmoreorlessputtheRegistryonthelegacylistand
you'renotsupposedtouseitanymore,mainlybecauseitcreatesamoredifficultdeployment
situationcomparedtousing.config filesinstead,forexample.
YouwillnormallyusetheRegistry classintheMicrosoft.Win32 namespaceastheentrypoint
whenworkingwiththeRegistryin.NET.
Using Active Directory
YoucanthinkofActiveDirectoryasthe"Registry, butitspansallthemachinesinyournetwork
andalltheinformationfoundonallmachines.Formanydevelopers,ithasbecomeapopularplace
forstoringconnectionstringsandsimilaritems.
ItwashardtoworkwithActiveDirectory(orratherADSI)fromVB6,butit'snowmucheasierin
.NET.YoustartbyusingtheDirectoryEntry classoftheSystem.DirectoryServices namespace
forfindinginformationintheActiveDirectory.
Using Object Construction (OC)
COM+1.0madeitpossibletostoreastringintheCOM+catalogforaspecificcomponentandthen
retrievethatvaluewhenanobjectofthecomponentwasactivated.Thisisstillausefulandviable
solutionforservicedcomponents.Asyouwillseeinthenextsection,IseeOCbothasasolution
forthepersistentstorageandalsoasanaccessmechanism.However,youcanalsouseitjustasa
persistentstoragemechanismsothatwhenyoufindthatyouneedtoreadthevaluefrom
persistentstorage,youinstantiateanobjectofthecomponentthatisusingOC,grabthevalue,
andstoreitintransientstorage.
Listing4.26showscodethatillustrateshowOCcanbeusedforservicedcomponents.Asyoucan
there,IoverridetheConstruct() method,andtheparametercalledconstructString isthe
stringthatcanbechangedintheComponentServicesExplorer.
Listing 4.26 Example Showing How to Use OC in a Serviced Component
Imports System.EnterpriseServices
<ConstructionEnabled _
(Default:="Hello world")> _
Public Class OcTest
Inherits ServicedComponent
Private m_Data As String
Protected Overrides Sub Construct _
(ByVal constructString As String)
m_Data = constructString
End Sub
'And some more methods...
End Class
Using a .config File
.config filesarefilesinXMLformatforholdingconfigurationdata.Theywereintroducedin.NET
asasolutionforhelpingwithXCOPY deployment.(WithXCOPY deployment,asingleXCOPY
commanddeploysanapplicationatanotherserver.TherearenoRegistrysettingsthathavetobe
written,noregistrationsofDLLs,andsoon.)
Youhavealreadyseenexamplesof.config filesinthischapterinListing4.4andListing4.6.
Thereyounotonlysawthat.config filesareusedforconfiguringthebuilt-intracingsolutionin
.NET,butalsohowIusedit toconfiguremyowntracingsolution.
Unfortunately,.config filesdon'tworksowellwithservicedcomponents..config filesarefor
EXEs,ratherthanDLLs,andthatisprettynatural.ConsideraDLLthatissharedbyseveralEXEs.
ItshouldprobablybepossibletousedifferentsettingsforeachEXE.Sofar,sogood.Theproblem
isthatCOM+serverapplicationsareexecutedbythesurrogateEXEcalledDllhost,andthereis
onlyoneEXEforallCOM+applications,soit'sproblematictousethesettingsfor,say,built-in
tracinginthe.config file.It'salsothecasethatDllhost isn'twritteninmanagedcode,andit
doesn'tknowthatitshouldloada.config atall.Thisiswhyit'salsoproblematictousethe
System.Configuration namespace.Finally,you willhavetokeepthe.config fileinthesame
directoryastheDllhost EXE,andit'slocatedinyoursystem32 directory.SomuchforXCOPY
deployment.
Note
Yetanotherproblemwithusing.config filesisthatthebuilt-intracingsolutionwon't
refreshthevaluesfoundinthe.config file.Instead,the.config fileisreadonlyonce
fortheprocess.ThisisnotabigproblemforaWindowsFormsapplication,butitmight
beahugeproblemforaCOM+application.
Instead,Ihavedecidedtouseacustomapproachforreading.config filesfromCOM+
applications.Iusetheformatof.config files,butIgivethemthenameoftheDLLtowhichthey
belong(samevalueasforAssemblyGlobal.exeOrDllName constant).FirstIthoughtabouthaving
the.config filestogetherwiththeDLLs,buttheGACcreatesaproblemforthis.Therefore,I
decidedthatthe.config fileswillthenbestoredinonespecificdirectory.Ihavedecidedtouse
c:\complusapps.ThenIusecustomcodeforparsingthe.config files,sothisapproachisquite
easyafterall.
Note
Aninterestingwhitepaperthatisnotonlyrelatedtoaddingsupportfordebuggingbut
alsoaboutconfigurationdataisAmitabhSrivastavaandEdwardJezierski'sMonitoringin
.NETDistributedApplicationDesign.
9
ThepaperdiscussesWindowsManagement
Instrumentation(WMI)andhowitcanbeusedforupdating.config files,whichisjust
anotheradvantageofthetechniqueof.config files.
Access Mechanism (and Transient Storage)
Youalsoneedtothinkabouthowtoaccesstheconfigurationdata.Youcan,ofcourse,gotothe
persistentstorageeachandeverytime,butthisisprobablytoocostlyifyouneedtogothere
often.Therefore,thefollowingareafewtechniquesthatI'dliketodiscussforprovidinganaccess
mechanismtotransientstorage.(Notethatthislistisnotexhaustive.)
N UsingtheSharedPropertiesManager(SPM)
N UsingObjectConstruction(OC)
N UsingaShared (static)member
N UsingtheThreadStatic attribute
N UsingObjectPooling(OP)
Note
Youcanalsoreadaboutserver-sidecachinginChapter8,whereI discussObjectPooling
inacontextsimilartothisone.
Onceagain,let'slookateachofthesetechniquesindetail,startingwiththeSPM.
Using the Shared Properties Manager (SPM)
TheSharedPropertiesManager(SPM)isanold,faithfulcomponentservicethatisstillaround.You
canuseitfromyourservicedcomponentsasadictionaryobjecttostorekeysandvalues,similar
totheApplication objectinASP.NET.However,theSPMcangroupthekeysinpropertygroups.
TheprogrammingmodeloftheSPMisabitdifferentfromotherdictionaryobjects,butit'sstill
quitesimple.YoucanseeasampleinListing4.27,whereavalueisreadfromtheSPM(orwritten
ifitwasn'tfoundintheSPM).
Listing 4.27 Reading a Value That Is Stored in the SPM
Imports System.EnterpriseServices
Public Class SpmTest
Inherits ServicedComponent
Public Function Test() As String
Dim theExist As Boolean
Dim aManager As New SharedPropertyGroupManager()
Dim aGroup As SharedPropertyGroup = _
aManager.CreatePropertyGroup("MyGroup", _
PropertyLockMode.SetGet, _
PropertyReleaseMode.Process, theExist)
Dim aProperty As SharedProperty = _
aGroup.CreateProperty("MyProperty", theExist)
If Not theExist Then
aProperty.Value = Now.ToString()
End If
Return aProperty.Value.ToString()
End Function
End Class
Using Object Construction (OC)
Intheprevioussection,ItalkedaboutOCasamechanismforpersistentstorage,butitisalsoa
mechanismforaccessingtransientstorage.RefertoListing4.26foracodesample.
Using a Shared (Static) Member
UsingaShared membervariableisbynomeansspecifictoservicedcomponents,ofcourse.It'sa
simpleandefficientmechanism.Itsmainproblemisthatyouonlyhaveonesingleinstancethatis
sharedbyallusersandtheircomponents,whichcanleadtoconcurrencyconflictswhenthevalue
isn'tread-only.Becauseofthis,youhavetoprotectthevaluebywritingthread-safecode.This will
resultinwaitstatesinahighlystressedenvironmentwhentheShared memberistobeusedall
thetime.Mostoften,itwon'tcauseyouanyproblems,butinsomesituations-asIsaid,under
extremeload-itmightbeashow-stopper.
Togetacheaperlockingsolution,youcanimplementaReaderWriter lockinstead.Thatmeans
thattheremightbeseveralreadersatthesametime,butonlyonewriter.Doyougetadjvu
fromthedatabasearea?YoucanfindmoreinformationabouthowtoimplementaReaderWriter
lockinthe.NETonlinehelp.
Note
Becausethischapterismostlyaboutdebugging,Imustwarnyouagainsttryingtowrite
thread-safecodemanually.Ifyoudoso,youmightcreateadebuggingnightmarefor
yourself,eithernoworinthefuture.Becarefulandmakesureyouknowwhatyou're
doing.
Using the ThreadStatic Attribute
It'spossibletouseTLSin.NETtoo,sothateachthreadwillhaveit'sownlocaldata.It'seasily
achievablebyjustmarkingaShared variablewiththeThreadStatic attribute.
Thiswillmeanthatyouwillhaveasmanyinstancesofthedataasyouhavethreads.Therewillbe
noblocking,becauseeachthreadwillhavethedataitneeds.Thismightalsomeanaproblemfor
youbecauseyouwastememory,butthatismostoftennotthecaseforconfigurationdata.
Using Object Pooling (OP)
ThefinaltechniquethatI'dliketodiscussforprovidinganaccessmechanismtotransientstorage
isusingObjectPooling(OP)inservicedcomponents.Ifyoustillrememberthetechniqueofthread-
specificglobalvariablesfromthedaysofVB6(andwhichIdiscussedearlierinthissectionabout
configurationdata),OPisanothertechniquethatcomesclosetothatsolution.
Notthatpooledobjectsaretiedtoaspecificthread,butyouwillhaveapoolofn numberof
objectswithOP,sothatyoucanavoidthesituationofseveralsimultaneousrequestsofthesame
valueleadingtowaitstates.Ontheotherhand,thelargerthepool,thelargertheamountofused
memory,ofcourse.
However,while itisanadvantagethatseveralinstancesminimizecontention,it'sadisadvantageif
it'snecessaryforallinstancestohavethesamevaluesallthetime.Ifitisnecessary,you'dbetter
useonesinglevalueinstead(unlessit'snota"fetchonce,usemanytimestypeofdata).
Note
BothObjectConstructionandObjectPoolingcanbeused,evenwhentheobjectislocated
inthecontextofthecaller,becauseneitheroftheseservicesarebuiltwithinterception.
Summary of Persistent Storage and Access Mechanisms
Notallaccessmechanismscanbeusedwithallpersistentstoragemechanisms.Table4.3presents
thepossiblecombinations.
Table 4.3. Possible Combinations of Access Mechanisms and Persistent Storage Mechanisms
SPM OC Shared Member 1hread Static OP
Databaseserver OK N/A OK OK OK
Registry OK N/A OK OK OK
ActiveDirectory OK N/A OK OK OK
OC OK OK OK OK OK
.config file OK N/A OK OK OK
AsyouseeinTable4.3,onlyOCisabitinflexibleasanaccessmechanismbecauseitissoclosel y
relatedtoitsstorage.Youcan lettheusersaffectOCasanaccessmechanismbyusingtheAdmin
APIforCOM+tochangethestoredvalue,butitissortofahack.Atleast,Idon'tthinkthiswas
thewayOCwassupposedtobeused.
How and When to Refresh the Cached Configuration Data
Finally,itmightbeaproblemdecidingwhentorefreshthecachedconfigurationdata.Asimple
approachistoencapsulatethedatasothateachtimethereisarequestforthedata,youcan
investigatewhetheracertainamountoftimehaselapsedsincethelasttimethedatewas
refreshed.Ifso,yougotopersistentstoragetoreadthedataandrememberwhenthishappened.
Ifyouuse,forexample,ObjectPoolingforcachingthedata,itmightbeagoodideatointroducea
two-phasetechniquesothatnoteveryobjectintheobjectpoolhastogotopersistentstorage.
Instead,youhaveonesinglepooledobject(of,say,classA)thatisresponsibleforthis,andevery
pooledobject(of,say,classB)willonlygototheobjectofclassAwhentheyfindoutthatit'stime
torefreshtheirdata.ClassAusesasimilaralgorithmsothatitdoesn'tgotopersistentstorage
eachtimeitiscalledbyaBinstance.Ifyouuseaschemalikethis,youhavetorememberthatthe
maximumtimefordifferentinstancestobeoutofsyncisthesumoftherefreshdelayoftheA
objectandtherefreshdelayofoneBobject.
Evaluation of Configuration Data Solution
Forsometypesofconfigurationdata,thedatawillbereadextremelyfrequently.Flagsfortracing
isagoodexampleofthis.Withthesetypesofconfigurationdata,probablythesinglemost
influentialofthefactorsdiscussedinChapter2,"FactorstoConsiderinChoosingaSolutiontoa
Problem,isperformance.Becauseofthis,Ihavedecidednottoprintmyevaluationinthisbook,
becauseIwillfinishthebookbefore.NETisavailableinversion1.Forthisreason,Iconsiderit
dangerousandmisleadingtorunperformancetests.Instead,youwillfindperformanceresultsand
myrecommendationregardingconfigurationdataatthebook'sWebsiteat
www.samspublishing.com.Youwill,ofcourse,alsofindallthecodetherefortherecommended
solution.
Note
ThisistheonlytimeinthebookthatIhaveendedadiscussionwithoutgivinga
recommendation,butasIsaid,thisproposalisextremelyperformancedependent,and
beforeV1of.NEThasbeenreleased,it'sjusttoodangeroustodecideonawinner.
Thesewereafewwordsabouttheaspectofaccessingconfigurationdata.Whenitcomesto
persistentlystoringconfigurationdata,Itendtofindthe.config solutionisthebestone.The
secondbestisusingthedatabaseserverorActiveDirectory.
Evaluation of Proposals
Itistimetoevaluatemyproposalsagain.Thistime,Iwillevaluatemytracingandlogging
proposalstoseehowtheystanduptothecriteriawediscussedinChapter2.
Evaluation of the Tracing Proposal
Ithinkmytracingsolutionismore"consumertype-friendlythanthe built-insolutionin.NET
becauseitworkswellandinexactlythesamewaywithASP.NETtoo.It'salsoeasytocollecttrace
informationfromseveralprocessesinonesinglelistoftracecalls.
It'sextremelyimportantthatthetracingsolutiondoesn'tcreatealotofoverhead,especiallywhen
everythinginthesystemisfineandnobodyislistening.Myproposalisatahighabstractionlevel
becauseitisaddeddirectlyinthesourcecode.It'snotwithoutpenaltiestoperformanceand
scalability,butstillIthinkthatinreal-worldapplications,theoverheadisnottoohigh.
Youcanfindtheresultsofseveralperformancetestsatthebook'sWebsiteat
www.samspublishing.com.Allthetestshave beenexecutedwithV1of.NET.Notethatyoushould
bepreparedtoseethattheoverheadisquitelargeforcallingemptymethods/storedprocedures,
buthowoftendoyouhavetheneedforthat?Notthatoften,Ihope.Youwillalsoseebythe
resultsthat thetracingsolutiondoesn'taddthatmuchoverheadtomethodsthatdorealwork.
Almostallthe"-abilitiesIdiscussedinChapter2-maintainability,reusability,productivity,
testability,anddebuggability,butnotreliability-willbenefitfromhavingtracinginthecode.The
productivitywilldegradeslightlybecauseoftheworkinvolvedinaddingthecalls,but,because
debuggingwillbesomuchmoreeffective,itwillalsohelpproductivityinthelongrun.Reliabilityis
hopefullynotadverselyaffectedeither,butyoushouldbeawarethatthereisalwaysagreaterrisk
forproblemspurelybecauseofextracode.
Coexistenceandinteroperabilityarealsoaddressedbytheproposal.Forexample,browsinga
completecallstackfromaVB6componenttoaVisualBasic.NETcomponenttoastoredprocedure
andallthewaybackcanbeveryhelpfulincertainsituations.
Whenitcomestosecurity,youmustbecautious.Whenyouaddtracecalls,youcanendup
openingupwindowstosensitiveinformationsothataskilledandinterestedpersoncouldlistento
passwordsandwhateverinformationyousendinthetracecalls.Youshoulddiscussthiswithyour
customerupfrontsothatyouestablishclearroutines.
Ihavesaiditbefore,butI'dliketosayagainthatthemainfocusofthischapterisn'tthetracing
toolitself,butrathertheconcept
What's Next?
.NETopensupalotofnewpossibilitiesforyoutodesignyoursystemanditsdifferentparts.You
havenewflexibility,butthatdoesn'tmakeiteasier.Somearchitecturalguidanceorinspirationis
neededforarchitects,teamleaders,anddevelopers.Inthenextchapter,Iwilldiscuss
architectures,bothinthebroadperspectivesuchastiersandlayers,andinthenarrowperspective
regardingcodestructures.
References
1.J.Robbins.Debugging Applications.MicrosoftPress;2000.
2.http://msdn.microsoft.com/msdnmag/;the"Bugslayercolumn.
3.http://www.sysinternals.com;DebugView.
4.K.Beck.Extreme Programming Explained: Embrace Change.Addison-Wesley;1999.
5.http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/odssql/ods_6_con_01_22sz.asp.
6.S.SundbladandP.Sundblad.Designing for Scalability with Microsoft Windows DNA.Microsoft
Press;2000.
7.T.Ewald.Transactional COM+: Building Scalable Applications.Addison-Wesley;2001.
8.http://www.razorsoft.net (seeTraceHook.NET).
9.A.JezierskiandE.Jezierski.Monitoring in .NET Distributed Application Design.
http://msdn.microsoft.com/library/en-us/dnbda/html/monitordotnet.asp.
Chapter 5. Architecture
IN THIS CHAPTER
N ThreeExamplesofArchitectures
N SampleApplication:AcmeHelpDesk
N MyArchitectureProposal:A.NET-AdjustedVersionofDNA
N NewConceptstoConsiderWhenCreatingaNewArchitectureToday
N PhysicalPartitioning
N ProposalsforStandardizedCodeStructures
N EvaluationofProposals
Sofar,wehavediscussedwaysinwhichyoucanpreventproblemsbyplanningandtestinginthe
beginningphasesofaproject.Oneexamplewastoaddsupportfortracingtoyourcomponents
andstoredproceduresearly.However,themostimportantaspectofaprojectintermsof
preparationisdecidingonitsarchitecture.Inmakingsuchpreparation,youshouldthinkinterms
ofscale.Ifyourprojecthasasmallscale(suchasonlyoneorafewendusers,ashortapplication
lifeexpectancy,andonlyonedeveloper),havingawell-thoughtoutarchitectureisn'tallthat
important.Ontheotherhand,ifyouknowthattheapplicationwillsupportmanyusersorifyou
wanttoprepareforfuturegrowthinanyway,agoodarchitecturalplaniscrucial.Andremember,a
shortlifeexpectancyforasystemcanactuallylastaverylongtime,makingitnofuntohavea
quick-and-dirtysystemtomaintainforyears.Meanwhile,.NETComponentServicesandSQL
Server2000arealsoverymuchaboutscale.Youwouldprobablynotconsiderusingeitherofthem
ifyouarenotexpectingtobuildalarge-scaleapplicationfornumeroususers.
Inthischapter,we'lllookatseveraldifferentarchitecturalstyles-adatabase-independentand
pureobject-orientedarchitecture,adatabase-centricarchitecture,andtheclassicWindowsDNA
architecture.We'llalsospendtimelookingatasampleapplicationbasedontheAcmeHelpDesk.
I'llthenpresentmyownachitectureproposal,which,asinearlierchapters,Iwillevaluatebased
onthecriteriaestablishedinChapter2,"FactorstoConsiderinChoosingaSolutiontoaProblem.
Inaddition,we'lllookatvariousarchitecturalfactorstoconsider,suchasphysicalpartitioning,and
I'llpresentasecondproposal,whichdealswithconsistentcodestructures.
Three Examples of Architectures
Asweallknow,avastnumberofarchitectureexampleshavebeenpresentedinliteratureand
magazinesoverthelastfewyears.Tonarrowthescope,inthischapterIwilldiscussonly
exampleswhererelationaldatabasesareusedbecausethatisthemosttypicaldatabasetechnique
forbusinessapplications.Thissectionwilllookatthreesuchexamples,adatabase- independent
andpureobject-orientedarchitecture,adatabase-centricarchitecture,andtheclassicWindows
DNAarchitecture.
Example 1: The Database-Independent and Pure Object-Oriented
Architecture
Membersofthepureobject-orientationcommunitybelievethatarchitecturesshouldbecompletely
databaseindependent.Inextremecases,the applicationonlytouchesthedatabaseatstartupto
readallthestateandatterminationtowritebackallthestate.Althoughthisstyleworkswellin
somesituations,itisnotsuitableforthetypicalmultiuserbusinessapplication.Thisisbecause
thereistoomuchdatatokeepinthememory,andthedatacaneasilybelost.Ifthestateiskept
attheworkstation,youwon'tseeotherusers'changesuntilthenextstartup.Ifthestateiskeptat
theserverandissharedbyallusers,youwillexperiencesynchronizationproblemsthatyouhave
tosolve.Meanwhile,ifthestateiskeptattheserverwithoneobjectmodelperuser,onceagain,
youwon'tseeotherusers'changesuntilthenextstartup,anditwilleatupmemoryfromthe
server.
Thestyleofpureobject-orienteddesigncouldbechangedslightlysothatatthestartofause
case,everythingneededisreadfromthedatabasetoapureobjectmodel,theobjectmodelis
workedon,andthentheresultingobjectmodeliswrittenbacktothedatabase.Thisisacommon
approachforbusinessapplicationstoo.Inthiscase,alargepartoftheworkwillconsistofmapping
betweenobjectsandrelationaldata.
Asyoucansee,inthisapproach,thedatabase'scapabilitiesarerarelytakenadvantageof.The
developersarelefttotakecareofsuchfunctionsastransactions,concurrencycontrol,querying,
andtraversingthedata.Becausemostnavigationisdoneintheobjectmodel,thejoincapabilities
ofthedatabase,forexample,arenotused.
Let'slookatanotherexampleofwhenanarchitecturelikethisisused.Supposeauserwantsalist
ofallhercustomerswhoselastnamesstartwiththeletterA,aswellastheirorderhistories.In
thiscase,thereshouldbeonecollection;let'scallitCustomerCollection,containingCustomer
objects.EachCustomer objecthasaninstanceofanOrderCollection,which,initsturn,contains
severalOrder objects.EachOrder hasacollectioncalledOrderRowsCollection thatcontains
informationaboutproductsfortheorder.Thisapproachcouldeasilyendupwithhundredsor
thousandsofobjectsforeachCustomer,allofwhichmusttraveloverthewirebetweendifferent
tiers.Usingobjectsoverthewirecanbeveryexpensive;thisisdefinitelytrueforVB6-created
COM objects,forexample,becauseonlyobjectreferenceswilltravelthen.Iftheobjectsare
serializedtoaflatstructure,sendingthemoverthenetworkwillbemoreefficient,butthe
serializationanddeserializationwillalsotakesometime.
ThemostrealisticandinterestingproposalIhaveseenforapureobject-orientedarchitectureis
theonePeterHeinckiensdiscussesinhisbook,Building Scalable Database Applications.
1
Inhis
proposal,heusesthedatabasecapabilitiestomakedata-nearoperationsmoreefficient.Still,he
doesn'tdiscussthemultiuseraspects,transactions,server-basedobjectmodels,northe
implicationshisproposalhasfordistributedsystems.Asyouprobablyhaveguessed,Idon'tthink
thisisagoodenougharchitecturefortheplatformwearegoingtouse.
Example 2: The Database-Centric Architecture
Somearchitectsanddevelopers-myselfincluded-aredeeplyenamoredwiththeprocessing
capabilities,suchasstoredprocedures,ofmodernrelationaldatabases.Thiswasespeciallytrue
withthesecondwaveofclientserver,whensomeormostofthelogicwasmovedfromtheclient
tothedatabaseserver.MichaelStonebrakerandPaulBrowndiscussthisintheirbook,Object-
Relational DBMSs: Tracking the Next Great Wave, Second Edition.
2
Theythinkofthedatabase
serverasanapplicationserveraswell.Forexample,theauthorssuggestthatonedatabaseserver
canactastheapplicationserver,whileanothercanberesponsibleforthedata.Onemeritofthis
approachisthatit'seasytorepartitionthelogic,becauseitcanbelocatedatanyofthedatabase
servers.
Amorerecentversionofthisapproachtodatabase-centricarchitecturewaspresentedby
Microsoft'scodesample,DuwamishOnline.
3
Here,Microsoftdemonstratedhowtocreatepartofan
e-commerceapplicationbyusingonlyXML/XSLandtheXMLsupportinSQLServer2000.
4
Indoing
so,theysucceededingettingverygoodperformance.However,adisadvantagewiththedatabase-
centricapproachesisthatattheendoftheday,agreatdealoflogicmustbelocatedintheclient,
andthis,inturn,causesseveralproblems,includingdeployment,heavyclientapplications,and
manyroundtripstothedatabaseserver.Althoughthiscouldbeagoodapproachforsimple
applications,Idon'tseeitasareallyflexiblearchitecturetosuitseveraldifferentclassesof
applications.
Example 3: The Classic Windows DNA Architecture
Microsoft'sfirstattemptatgivingarchitecturalguidancewaswhentheysuggestedusingWindows
DNAasawaytobuildn-tierapplications.Comparedtopureobject-orientedarchitectures,this
architecturepaidmoreattentiontophysicalaspects-forexample,valuetypesinsteadof
referencesweresentoverthenetworkbetweentiers.TheconceptwastargetedatusingMicrosoft
productsandtechnologies,suchasASP,COM+,andSQLServer.Therehavebeenthousandsof
applicationsbuiltinthisstyle.Theapproachisprettymuchstructureduponlayers.
5
Figure5.1
showsapictureyouprobablyhaveseenmoretimesthanyoucanrememberdescribingthe
differentlayersaccordingtoWindowsDNA.
Figure 5.1. Classic Windows DNA tiers: Presentation, Business Logic, and Data.
WhatisalsotypicalofWindowsDNAisthatitisservicefocusedratherthanobjectfocused.WhenI
discussedthepureobject-orientedarchitectureearlier,Igaveanexampleofauserwhowantsto
havealistofallhercustomerswhoselastnamesstartwiththeletterAandtheirorderhistories.
WithWindowsDNA,thisinformationwouldtypicallybefoundbyamethodthatreturnsasimple
structure,forexample,asanarrayoranADORecordset,withalltheinformation.
InthemostclassicdefinitionofWindowsDNA,allbusinessrulesshouldbeputintheBusiness
Logiclayer,anditshouldbepossibletoeasilyportanapplicationtootherdata- basessimilarto
SQLServer.StenSundbladandPerSundblad,intheirbookDesigning for Scalability with Microsoft
Windows DNA,
6
changedtherecommendationslightlysothatthedatabaseisresponsibleforat
leastsomeofthebusinessrules.SomeofthecodesampleapplicationsprovidedbyMicrosoft
(calledDuwamishOnline
3
andFitchandMather
7
)weremuchmoreeagertousestoredprocedures
thanclassicWindowsDNA.AsyouwillfindwhenIdiscussmyarchitectureproposallaterinthe
chapter,IthinkWindowsDNAhasalotofmerits.Althoughmyarchitectureproposalisdifferent
fromWindowsDNA,itisinspiredbyit.
Sample Application: Acme HelpDesk
BeforeIpresentmyownarchitectureproposal,I'llbrieflydiscussthesampleapplicationthatI'm
goingtousefortheexamplesfromnowon.
Overthecourseofmycareer,Ihavebuiltseveraldifferentapplicationsforerrandhandlingfor
helpdesks,amongotherthings.Chancesarethatyouhavebuiltsimilarapplicationsorhavehad
somepracticalexperiencewithone.Inanycase,itshouldbeeasyforyoutoquicklygetanideaof
thenaiveversionofthesampleapplicationI'mgoingtopresenthere.First,let'stakealookatthe
sampleapplication'srequirements.
Sample Application Requirements
Someof therequirementsforthissimplifiedversionoftheapplicationareasfollows:
N Usersshouldbeabletoreportanerrand,therebyrevealingalotofinformationaboutthe
probleminquestion.
N Theproblemsolversshouldbeabletologallactionstakentosolvetheproblem.
N Theproblemsolversshouldbeabletoroutetheresponsibilityofsolvingtheproblem.
N Theuserwhoreportedtheerrandshouldbeabletoseehowtheproblemhasattemptedto
havebeensolvedpreciously.
Nowlet'smoveontothedatabase schema.
Sample Application Database Schema
Figure5.2showsthedatabaseschemaofthesampleapplication.Asyoucansee,manycolumns,
tables,andrelationshipsaremissingfromthisfigure.However,thefigureismeanttoprovidea
simpledatabasetoshowarchitectureandothertechniques.
Figure 5.2. The simple database schema for the sample application Acme HelpDesk,
showing the errand and action tables.
Fromthispresentationofthesampleapplication,youmightthinkthatIbelievemodelingshouldbe
donefromthedatabaseup.Thisisnotthecase.Iamshowingitthiswayonlytoprovidea
backgroundfordiscussingthearchitecture.
Thetwoextreme-andquitepopular-ideasformodelingareto
N Createthedatabasemodelmoreorlessautomaticallyinaccordancewithhowthe
componentswerecreated
N Createthecomponentsmoreorlessautomaticallyinaccordancewithhowthedatabase
modelwascreated
Asalways,Ithinktheanswerliessomewhereinbetween,andthatgoesforthemodelingexample
aswell.Databasemodelingissomethingofaparallelactivitytoobjectmodeling.Typicalquestions
facedare:Whatmethodsareneeded?Whatdataisneededforthisusecase?Whatmustthisuse
caselookliketosupportthisdata?andsoon.
My Architecture Proposal: A .NET-Adjusted Version of DNA
AsIwritethisbook,thereisn'tmuchguidanceonarchitectureinthe.NETworld.Giventhisdearth,
I'dliketostickmyneckoutandpresentwhatiscurrentlymypreferredarchitectureproposal.In
doingso,IwillcombinemyexperiencewithdifferentarchitecturesIhavesuccessfullyimplemented
overtheyearswithmyknowledgeof.NET.First,we'lldiscussthekeyassumptionsunderlyingmy
proposal,andthenwe'lllookatthearchitectureproposallayerbylayer,citingotherimportant
considerations.
My Proposal: Building Blocks
BeforeIexplainmyarchitectureproposalindetail,it'simportantthatyouunderstandafewofits
keyassumptionsandcomponents.
Serviced Components
Throughoutthisbook,Iwilluseservicedcomponentstoassistwithenterpriseinfrastructure
services.ExamplesofservicesthatIwilluseinsuitablesituationsareasfollows:
N Objectpooling
N TransactionsandJust-In-Time(JIT)activation
N Synchronization
Moreorlessautomatically,Iwillalsoreceive
N Resourcepooling
N Administrativeandproductionsupport
Thechoicetouseservicedcomponentswillaffectthearchitecturebecauseservicedcomponents
setanumberofrulesfor.NETcomponents,suchasstatingthatnondefaultconstructorsand
sharedmethodsasentrypointscan'tbeused.
Note
Whenservicedcomponentsareused,.NETsecuritymustbeopenedup.Ontheother
hand,it'squitecommontobeforcedtotrustserver-sidecomponents.Withorwithout
.NETsecurity,thiscanbeasecurityrisk.
Stored Procedures
Istronglybelievethatitisextremelyimportanttousestoredprocedures.Whilethereare
drawbacks,suchaslackofportabilityandarequirementforT-SQLskills,therearemanymore
advantagesinusingstoredprocedures,includingthefollowing:
N Theyaretypicallythemostefficientwaytoaccessthedatabase.
N Theycanreducenetworkroundtrips.
N Theyareeasytochangeindeployedapplications.
N Theymakeitiseasiertotunetheperformanceofyourdataaccesscode.
N Theyprovideabetterwaytogiveoutpermissionsratherthantogivepermissionsoutfor
basetables.
N Theyallowthedatabaseschema(andschemachanges)tobehidden.
N Theyhelptocentralizeallthedataaccesscode.
Note
BecarefulifyouusedynamicSQLwiththehelpofEXEC('string') orSP_EXECUTESQL()
withinyourstoredprocedures,becausetheusermustbeauthorizedtousetheobjects
thatareused-beingauthorizedtousethestoredprocedureisn'tenough.Agoodsolution
istobeveryrestrictiveregardingthetables,buttouseviews/user-definedfunctions
(UDFs)inthedynamicSQL,andthengiveoutenoughrightstotheviews/UDFs.
Ifalldatabaseaccessisdonethroughyourstoredprocedures,yougetacontrolledsetofentry
points.Whyisthisgood?
N Auditingiseasilysolved.
N Disconnectedpessimisticlockingiseasilysolved.
N Data-closebusinessrulescanbeputinstoredprocedures.
N Thereisnoneedfor triggers,whichisgoodfordebuggabilityandmaintainability.
Note
Insteadofusingthepublicstoredproceduresasentrypointsforauditing,youcanusethe
transactionlogitselfinSQLServer.Thelogcan'tbeuseddirectlywithordinarySELECT
statementsorwithaneditor,butthereareproductsthatcanhelp.Oneproblemwiththis
solutionisifyouwanttoauditSELECTstoo.Youhavethesameproblemifyouwantto
auditothernonloggedoperationswiththissolution.
I'mnotconvincedthattheuse ofstoredproceduresisonlynegativeintheportabilitysense.When
Ihavereviewedsystemswherestoredprocedureshaven'tbeenusedbecauseofportability
reasons,theyhavehadtheSQLcodespreadallovertheplace,andtheSQLcodehasbeenfullof
database-specificconstructs.IfyouputallSQLinstoredprocedures,youhaveitatleastatone
place,andifyoustaySQL92-compliantwhenpossible,alotofthecontentofyourstored
proceduresisportable.
InChapter7,"BusinessRules,IwillexplainwhyIthinkstoredproceduresareagoodandscalable
placeforevaluatingsomerules.Evenso,adisadvantageisthattheconsequencesaremore
seriouswhenyouwritebadcodeinstoredproceduresthanwhenyoudosointhecomponents.For
example,addaloopinastoredprocedurethatgoesthroughallrowsinatableandrepeatthis
severaltimes.Thencomparethattodoingthesamethinginthecomponent,byloopingthedatain
aDataSet.Inthefirstcase,youareseverelyaffectingwhatispossiblytheonlydatabaseserver.
Inthelattercase,youcan justaddonemoreapplicationserverifthroughputisbad.
Are Stored Procedures of Less Importance
to Oracle?
Ihavegottentheimpressionseveraltimesfromdifferentsourcesthatitis
moreimportanttousestoredprocedureswithSQLServerthanwith,say,
Oracle.BecauseIdon'thavecurrentexperiencewithOracle,Iaskedmy
friendandexperienceddeveloper,MichaelD.Long,aboutthis.Hetoldme
thatstoredproceduresarejustasimportantforoperationsthatwill
updatealargenumberofrows."OracleDataBaseAdministrators(DBAs)
willbeoftheopinionthatstoredprocedureswillimproveoverallscalability
ofthedatabaseserver,Michaelsays,"butIhaveobservedthatthe
inefficiencyoftheclientlayercanhavejustasmuchimpact.
Inaddition,duringMichael'sbenchmarkingofOracle7.3.4.x,8.0.5.x,and
8i(8.1.5and8.1.7),hetoldmeheobservedthatforoperationsinvolving
asinglerowINSERT orUPDATE,adynamicSQLstatementusedfewer
resourcesandrequiredlesstimethancallingastoredprocedureto
performthesameactivity.Michaelsays:"Thecharacteristicismoreaside
effectoftheclientstackthantheefficiencyofOracleServer.Thechatter
backandforthbetweentheclientandserverisgreaterwhensettingup
thestoredprocedure.TheLucentDBAshadnotedstrangeSQLstatements
beingexecuted;Oracle'sBugDiagnosisandEscalationteamlaterfound
theseweregeneratedbytheMicrosoftdataaccesslayer.
ADO.NET and DataSets
TherewillmostcertainlybealotofdiscussionregardingwhetherADO.NETDataSetsaretheright
mediumforsendingcollectionsofdatabetweenlayersandtiers,justastherewaswithADO's
RecordSetsinVB6.InVB6,IactuallypreferredusingADO'sRecordSets,eventhoughtheywerea
bitheavyandperformancewasnotoptimal.Themainreasonsweretheireaseofuseandbuilt-in
metadata.Iwillcontinuethattraditionin.NET,butIwillusetypedDataSets.Ifyou'relikeme,
Option Strict makesyoujumpforjoy,asdoesthetypesafetyyouhavewhenworkingwith
DataSets.Inaddition,thankstoDataSets,Icanavoidmyusualtricksforsendingseveral
RecordSetsatonce;DataSetshasthisbuiltinwithitsDataTables,whichsimplifiesmycodea
greatdeal.Idorespecttheopinionthatdevelopersdon'twanttobetoodependentonADO.NET
becauseADOchangedoftenandcausedalotofproblemsbecauseofthis.Thiscanbeareasonfor
notsendingaroundDataSets,buttobuildacustomstructureinstead.
Ifyoudon'tlikeDataSetsbutwanttobuildyour ownstructureforsendingthedata,youcanstill
usemyproposedarchitecture.Inthiscase,besuretouseDataReaderstograbthedataoutofthe
databaseinsteadwhenyoumoveittoyourcustomstructure.
Note
IwilldiscussADO.NETandDataSetsinmoredepthinChapter8,"DataAccess.
My Architecture Proposal Tier-by-Tier and Layer-by-Layer
AsIhavesaid,myproposalismostlyinspiredbyWindowsDNA.However,itismoredatabase
focusedthanisWindowsDNA;thatis,itisfocusedonsolvingallactionswithonesingleroundtrip
betweentiers.Inaddition,Ihaveadapteditto.NET.Figure5.3isanoverviewofmyarchitecture
proposal'sdifferentlayersandtheirrelationships.Thefigurealsoshowstiermembershipforthe
differentlayers.
Figure 5.3. My architecture proposal's different tiers, layers, and their relationships.
Thefirstimpressionyougetmightbeoneofoverkill.Pleaserememberthatthisarchitecture
shouldbeusedasastartingpoint,andoftenseverallayerswillbeskipped(ormergedtogether).
It'sextremelyimportantthatyouadaptthearchitecturetoyourspecificapplicationandsituation.
Note
Figure5.3showsonlythelayers(packages,inUnifiedModelingLanguage[UML]
parlance)regardingthemaincallstackforusecases.Therewill,ofcourse,beseveral
helperclassesandpackagesaswell.Sofar,youhavebeenintroducedto
Jnsk.Instrumentation inChapter3,"Testing,andChapter4,"AddingDebugging
Support.
Assembly and Layer
Layersareoftenimplementedasseparateassemblies,butIcanthinkofputtingtheConsumer
layerandConsumerHelperlayerinoneassembly(orexe)andtheDomainlayerandthePersistent
Accesslayerinanassembly(ordll).Thisisespeciallythecaseforsmallersystemsand/orwhenit's
notimportanttohavethepossibilitytodistributeanddeploychangedlayersinisolation.Evenso,
thedefaultsolutionistoleteachlayerbeanassemblyofitsown.
Proposal Used for Sample Application Acme HelpDesk
IfIapplytheconceptualarchitecturetothesampleapplicationAcmeHelpDeskdi scussedearlier,it
willappearasinFigure5.4.Asyoucansee,Ihaveprovidedphysicalnamesinthepackages.The
namespaces(andnamesofassemblieswithexeordllasextension)willbe
Figure 5.4. My architecture proposal applied to the sample application Acme HelpDesk.
N Acme.HelpDesk (fortheConsumerlayer)
N Acme.HelpDesk.ConsumerHelper (fortheConsumerHelperlayer)
N Acme.HelpDesk.Application (fortheApplicationlayer)
N Acme.HelpDesk.Domain (fortheDomainlayer)
N Acme.HelpDesk.PersistentAccess (forthePersistentAccesslayer)
Iwillprovideathoroughdiscussionabouteachlayershortly.
Note
Iwishwehadsuchathingasnamespacesforstoredprocedurestoo,butunfortunately
wedon't.Iprefixmypublicstoredprocedureswitha_ tocategorizethestored
procedures.
Let'snowdelvedeeperintothedifferenttiersandlayersofmyarchitectureproposal.Inthis
discussion,Iwilluseanexampleinwhichtheuserwantstolistallerrandsthathavesymbolic
namessuchas"Printer*orallerrandsforaspecificreporterbeingreportedbetweentwodates.
Thentheusercanselectoneoftheerrandsinthelisttoseeallthedetailedinformationaboutthat
errand,includingitsactions.
Consumer Tier
Theconsumertiercandifferalot,dependingonwhichconsumertypeisused.ForWindowsForms,
itistypicallyoneexecontainingboththeConsumerlayerandtheConsumerHelperlayer.ForWeb
Formsitwillbeadll,onceagaincontainingbothlayers.Asmentionedearlier,anothertypical
solutionistolettheConsumerHelperlayerbeinaseparatedll.
Consumer Layer
AssumingtheconsumerisbuiltwithWindowsForms,theConsumerlayerwillbemadeupof
WindowsFormsclasses.ThepurposeoftheConsumerlayeristoprovidepresentationservicesfor
theuserinthiscase.IprefertokeeptheConsumerlayerasthinaspossibleandtodelegate
severalresponsibilitiestotheConsumerHelperlayer.
Consumer Helper Layer
Asthenameimplies,themainpurposeoftheConsumerHelperlayeristohelptheConsumer
layer.ThislayerhelpstheConsumerlayermostlybyhidingcomplexity,butalsobyproviding
services.Theremainderofthissectionpresentssomeofitstypicaltasks.
Note
TheConsumerHelperlayerisnotahostforbusinessrules!Eventhoughitmightbe
tempting,don'taddanybusinessrulestothislayer.Wewilldiscussbusinessrulesin
greaterdepthinChapter7.
Hiding Factories
TheConsumerHelperlayerhidesfactories(oratleasttheuseoffactories),whichcanbevery
importantbecausecustomconstructorsaren'tallowedforservicedcomponents.Becausecustom
constructorsaren'tallowed,servicedcomponentswillstillhavetouseatwo-stepinitializationto
getinstance-statefromtheclientsothattheclientwillfirstcreatetheinstancewithNew() and
thencallanInit() withtheneededparameterstoprovidethestate.ObservethattheConsumer
Helperlayerclasscanhavenondefaultconstructorsbecauseitisnotaservicedcomponent.
It'sworthmentioningthatoften,allneededinformationfromtheclientissentovertotheserviced
componentineverycallinstead.ThisisamustiftheservicedcomponentisusingJITactivation,
becausethestatewillbelostafterthedone-bitofthecontextobjecthasbeensetandthemethod
isexited.Theconsumerwillnotknowthatithastoprovidethestateagain.Idefinitelypreferto
designinsuchawaythatIcanchangetheattributevaluesregardingJITactivationornot,without
breakingthecode.
Factorieshaveseveralotheradvantages(mostofthemduetocodecentralization),includingthe
following:
N Theyeasilytakeadvantageofpartitions(whichisanewfeatureinCOM+1.5).
N Theycanhidewhethertheservicedcomponentisusedasaqueuedcomponent.
N Theyhidewhatclassisprovidingtherealimplementation.
Hiding Communication over the Network
Remotingin.NEThasseveraladvantagesoverDCOM,butonedrawbackisthatitisless
transparent.Therefore,agoodsolutionistohidethechosencommunicationmethodinthe
ConsumerHelperlayer.Thisway,theConsumerHelperlayeristheonlylayerthatknowswhether
therewillbeanetworkcall,whetherDCOMorremotingwillbeused,whatchanneltypetousefor
remoting,andsoon.
TheConsumerHelperlayercanalsohelptomakenetworkroundtripsmoreefficientbyonly
serializingrowsinDataSetsthathavebeenchanged.
Locating Presentation Logic and Helping with Input
Trytomaketheformclassesas"thinaspossibleandfactoroutlogic.Agoodplacetoputthe
logicisintheConsumerHelperlayer,becauseseveralformscantypicallyshareonehelperclass.
TheConsumerHelperlayercanalsoprovidebasicinputhelpsothattheenduserhasasmallerrisk
ofgivingincorrectdatathatwillbecaughtbythebusinessrulesin"laterlayers.
Hiding Consumer-Side Caching
Insomesituations,it'svaluabletohaveconsumer-sidecaching.Ifitis,theconsumerlayer
shouldn'tbeawareofitorhavetocodeappropriatelyforit.Instead,theConsumerHelperlayer
canhideconsumer-sidecachingsothatwhentheConsumerlayerasksforaspecificpieceofdata,
theConsumerHelperlayerprovidesit.IftheConsumerHelperlayerfindsthedatainthecacheor
askstheApplicationlayerforit,itdoesn'tmattertotheConsumerlayer.
Note
Inearlierattemptswithconsumer-sidecaching,Ihadacacheclassthat,forexample,the
UserInterface(UI)formfirstcheckedtoseeifitwasuptodate.Ifitwasn't,the
Applicationlayerwascontacted.Inthiscase,thecachingdefinitelywasn'ttransparentto
theconsumer.Itwasamainingredientoftheprogrammingmodel.Iconsiderthisa
mistake.Informationhidingispreferable!
Translating Error Messages
TheConsumerHelperlayercanhelptranslateerrormessagesnotonlybetweenlanguages,but
alsofromprogrammer-friendlymessagestoenduser-friendlymessages.
Acting as Proxy, Hiding Stateless Business Tier
TheConsumerHelperlayershouldbedesignedsothatcallsbetweentheConsumerhelperandthe
servicedcomponentarefewandefficient,becausetherewilloftenbeprocess-to-process
communicationbetweentheConsumertierandtheBusinesstier.Thisrequiresthestyleof"one
methodcallperusecase,whichisefficientbutlessintuitivethananordinaryobject-basedstyle
wheresomepropertiesaresetandthensomemethodsarecalled.Thehelperobjectwillactasa
proxyandcanhidethe"largemethodcallsandprovideanordinary,object-basedstyleinstead.
Note
YoucanreadmoreabouttheProxydesignpatterninDesign Patterns: Elements of
Reusable Object-Oriented Software byErichGamma,RichardHelm,RalphJohnson,and
JohnVlissides.
8
Recovering from Problems
TheConsumerHelperlayercandiscoverandre-createaninstancewhentheserver-sideinstanceis
lostbecausetheCOM+applicationhasbeenshutdown.TheConsumerHelperlayercanalsohelp
torecoverfromdeadlocks.
Note
Iwilldiscuss furthertheproblemswithshutdownanddeadlocksinChapter9,"Error
HandlingandConcurrencyControl.
AtypicalConsumerHelperclasscouldlookasshowninFigure5.5.Thereyoucanseethe
ErrandBrowsingHelper class.Inthisexample,onlythree(ofwhichtwoareoverloaded)methods
areshownforfetchingalistoferrandsandfetchingalldetailsaboutaspecificerrandbyID.
Figure 5.5. Example of a Consumer Helper class.
AsyoucanseeinFigure5.5,ErrandBrowsingHelper inheritsfrom
Jnsk.Consumer.ConsumerHelper.Jnsk.Consumer.ConsumerHelper implementsIDisposable;that
way,theamountofcodeforErrandBrowsingHelper willbereducedandthepatternissimilarto
howtheservicedcomponentsarebuilt.It'salsothecasethattheJnsk.Consumer.ConsumerHelper
usestheTemplateMethodpattern
9
toforcethesubclasswiththehelpofMustOverride towritea
Dispose() method.
Note
IwillcomebacktotheDispose() problemlaterinthischapter,whereyouwillalsofind
codefortheentiresolution.
TheConsumerHelperclass(suchasErrandBrowsingHelper)willoftenmirrorthe"parallelclass
intheApplicationlayer,butmostoftentherewillbemoremethods.Furthermore,asIsaidearlier,
eventhoughthisisn'tthecaseinFigure5.5,itwillbecommontohavepropertiesthathidethe
factthattheApplicationlayerisbeingoperatedonby"largemethodcallswhereallstateis
providedineverycall.
Business Tier
Nowlet'sturnourattentiontotheBusinesstier,whichwillbebuiltwithservicedcomponentsand
executedatanapplicationserver.Thistierismadeupofthefollowinglayers:
N Applicationlayer
N Domainlayer
N PersistentAccesslayer
TheConsumertierwillonlyknowaboutandinteractwithoneofthelayersinthistier,namelythe
Applicationlayer.
Application Layer
ThefirstlayerintheBusinesstieriswhatIcalltheApplicationlayer.It'sverysimilartowhat
otherscalltheProcesslayer,
9
theFacadelayer,
6
theWorkflowlayer,
3
andsoon.Icallthislayer
theApplicationlayerbecauseitistheApplicationProgrammingInterface(API),asfarasthe
consumerknows.
ThepurposeoftheApplicationlayeristoprovideaclassforeachusecase.Whenthesystemis
modelled,theApplicationlayeristhefirstBusinesslayertobemodelled,typicallyalongwiththe
tablesinthedatabase.Inaddition,theApplicationlayerwilltalktotheDomainlayerandtothe
PersistentAccesslayer.ApurelayeredmodelwouldimplythatallcallsgototheDomainlayer
instead.ThemostimportantreasonforsometimesskippingcallingtheDomainlayer(eventhough
youhavedecidedthatyouneedthatlayerinyourapplication)isthatreadingfromthedatabase
wouldotherwisetypically onlybeaforwardingofthecallthroughtheDomainlayer.Thedrawback
is,ofcourse,thatthePersistentAccesslayerisvisibletotwolayers.
It'simportanttonotethattheApplicationlayershouldbeconsumerindependent.Itshouldbe
possibletousethesamelayerfromdifferentconsumertypesatthesametime.InFigure5.6,you
canseeanexampleofatypicalclassintheApplicationlayer.Here,theusecasefetchesalistof
errandsandcollectsalltheinformationaboutasingleone.
Figure 5.6. Example of a class in the Application layer.
AsyoucanseeinFigure5.6,ErrandBrowsing isderivedfromServicedComponentHelper,and
ServicedComponentHelper isderivedfromServicedComponent.ServicedComponent willprovide
theimplementationforIDisposable.ServicedComponentHelper takescareofmostofthe
Dispose() implementationforitssubclasses.(TheTemplateMethodpatternisused.)
Thislayerisdefinitelyacorelayerandshouldn'tbeskippedinsmallsystems.Howyoudesignthis
layeriscrucial.Thisisbecauseyouwill"blenditintotheConsumertierbecausethislayerwillbe
theentrypointfortheConsumertier.It'seasiertochangethestyleofthelayersthatcomeafter
theApplicationlayer,soit'simportantyougetthislayerright.
Manyusecaseswillusethesamemethods,suchasFetchProblemSolvers().Ifinditconvenient
tohavethatmethodinalltheusecasesthat"want it.Theconsumerprogrammerwillfind
everythingrelatedtoausecaseinoneclass.ThentheApplicationlayerclasswillcallageneral
methodthatwilldotherealwork.
Ioftenskiptousinguser-definedinterfacesforclassesintheApplicationlayer mainlybecauseall
oftheusecasesandclasseswilllookdifferentfromeachother.Butasyouwillseelaterinthis
chapter,ifyouliketoprepareforhavingunmanagedconsumersviainterop,youshoulduseuser-
definedinterfacestoworkwiththeclassesintheApplicationlayertoo.
Domain Layer
OtherauthorsanddevelopersoftencallthislayertheEntitylayerortheBusinesslayer.Ipreferto
callittheDomainlayerbecauseitdealswiththeconceptsintheproblemdomain.Thepurposeof
thislayerisnotonlytovalidaterules,butalsotoprovidecorealgorithms.BecausemostDomain
classeswillhavesimilarinterfaces,Iuseuser-definedinterfacesalotintheDomainlayer.Figure
5.7showsanexampleofaDomainclassthatimplementsoneuser-definedinterface.
Figure 5.7. Example of a class in the Domain layer.
AsyouseeinFigure5.7,Iusedaprefixdo fortheclassname.Ithinkthat itdoesn'thaveabig
drawbackbecausethatnameisinternaltotheBusinesstier.Theadvantageistoeasily
differentiateclassesfromdifferenttiers.ClassesinthePersistentAccesslayerwillhavepa as
prefix.
Note
TheexamplesintheprevioussectionsfromthedescriptionsoftheConsumerlayersand
theApplicationlayerdealtwithbrowsingerrands,whichdonotinvolvethislayer.The
ApplicationlayerwillcalldirectlytothePersistentAccesslayerinstead.
AlsoasyouseeinFigure5.7,IshowedthattheDomainclassesinheritfrom
ServicedComponentHelper justastheApplicationclasses.
AnothersolutionistoletthemethodsintheDomainclassesbeShared.Thenyouwon'tinstantiate
theclasses,andtheywon'tinheritfromServicedComponent. EverythingaboutDispose() isgone;
youdon'thavetothinkaboutCOM+contextsandthemethodsoftheShared classwillbe
extremelyfasttocall.Thedrawbackislessflexibilityandthatyouhardcodetheknowledgethat
themethodsoftheclassesareShared.YoualsohavetoavoidoratleasttakecarewithShared
variablesbecauseofmultithreadingaspects.Youcan'tuseuser-definedinterfaceseither,anda
Shared methodcan'tbeanentrypointforremoting.Evenso,Ithinkthisisoftenaverygood
solutionfortheDomainandPersistentAccesslayers.Asamatteroffact,Imostoftenpreferthat
solution.
InFigure5.8,youcanseethediagramofFigure5.7redrawnshowingtheuseofShared methods
instead.
Figure 5.8. Example of a class in the Domain layer when Shared methods are used.
TheclassesintheApplicationlayeraresaidtobeuse case-specific; thatis,thereisoneclassfor
eachusecase.TheclassesintheDomainlevelareusedbyseveralusecases(andthatalsogoes
forthelaterlayers).Insmallersystems,IhavesuccessfullyskippedtheDomainlayerandhave
movedtheresponsibilitiestostoredproceduresinstead.
What Security "Style" Should I Use?
Thereareseveraldifferentsecurity"styles,ordesigns,tochoosefrom,
buttheoneIusemostoftenislettingtheenduserbeauthenticatedby
theApplicationlayer,whichwilluseCOM+securityatthecomponent
level.Then,theDomainandPersistentAccesslayerswillnot useCOM+
securityatthecomponentlevel.It'snotpossibletousecomponentlevel
securityforyourCOM+applicationifyouwanttoco-locatetheobjects
withinthecontextoftheircaller.
ThisgivestheimplicationthatiftheDomainlayeror"laterlayersneed
informationabouttheuser,thatmustbeprovidedfromtheApplication
layerasparameterstothefollowinglayers.Forexample,theApplication
layercanfindtheaccountnameofthecurrentuserby calling
EnterpriseServices.
SecurityCallContext.CurrentCall.DirectCaller. _
AccountName.ToString().
AsyoucanseeinFigure5.7,Ihavesimplifiedthemethodsignatures
somewhatandIhaven'tshownparametersforsendingoveruser
information.
IfCOM+Role-BasedSecurity(RBS)can'tbeusedbecause.NETRemoting
isneeded,IstillthinkthattheApplicationlayeristheplaceforchecking
authorizationinyouralternativesolution.
Whengoingtothenexttier,namelytheDatatier,Idefinitelyprefernot
touseaccountsforpersonaluserstologintoSQLServer.Instead,the
COM+applicationwilluseonededicateduser.Thankstothis,connection
poolingwillworkasplanned.Onceagain,thedrawbackisthatuser
informationmustbesentasparameters.
Persistent Access Layer
ThemostcommonnameforwhatIcallthePersistentAccesslayerinothersourcesistheData
Accesslayer.Thislayerhidesalldetailsabouthowdifferentstoredproceduresarecalled.It'skind
ofahelperfortheBusinesstier, similartotheConsumerHelperlayerdiscussedearlier.
Note
InearlierarchitecturesIhaveused,IhadthePersistentAccesslayerberesponsiblefor
transactions.IhavenowdecidedtotransfertheresponsibilitytotheApplicationlayer
instead.Becauseofthis,thereisnoneedtosplittheclassesinthePersistentAccess
layerforwritingandreading.Instead,bothtypesofmethodswillbelocatedinthesame
class.WewilldiscussthisinChapter6,"Transactions.
BetweenthePersistentAccesslayerandthePublicStoredProcedureslayer,weoftenhave
machine-to-machinecommunication.BecausethePublicStoredProcedureslayerisbuiltwithjust
storedprocedures,thatcommunicationwillnormallybeefficient.Unfortunately,withouttricks,
storedprocedurescan'ttakeseveralrowsasparameters.OneofthereasonsIcreatecomplete
scriptsatthebusinesstieristhatitwillbesentinonecalltothedatatiertoreducethenumberof
roundtripsbetweenthosetiers.Onceagain,IwilldiscussthisindepthinChapter8.
UntilitistimetoinvestigatetheinnerworkingsoftheclassesinthePersistentAccesslayer,you
canseeanexampleofaclassinFigure5.9.Asyousee,itoftenimplementsseveraldifferent
interfaces.
Figure 5.9. Example of a class in the Persistent Access layer.
Note
Atfirst,theinterfacesshowninFigure5.7andFigure5.9maylookstrangewithregardto
theparametersbatchCommand,fetchType,andfilter.ThisisbecauseofthepatternI
useforcallingthestoredproceduresinthedatabase.TheDomainlayerandthe
PersistentAccesslayerwillbuildupanSQLscriptthattheApplicationlayerwillactasa
controllerwhenbuilding.Therealcalltothedatabasewillbedelayeduntilthecomplete
scripthasbeenbuilt.ThispatternwillbediscussedinChapter8.
RememberwhatIsaidaboutShared classesinthesectionabouttheDomainlayer.Thatgoesfor
thePersistentAccesslayertoo.InFigure5.10youcanseeadiagramsimilartotheoneinFigure
5.9,butthistimeonlyShared methodsareused.
Figure 5.10. Example of a class in the Persistent Access layer, only with Shared
methods.
Note
AsyoumayrememberfromFigure5.2,therewasatablecallederrand andalsoa
PersistentAccessclassthatreadsfromandwritestothattable.However,pleasedon't
cometotheconclusionthateverytableshouldhaveaclassinthePersistentAccesslayer,
becausethisismostoftennotthecase.Lookuptables,forexample,willusuallyonly
haveonePersistentAccessclass.Andthereisnothingthatstopsmefromletting
paErrand callthesprocs fortheaction tabletoo.
SQL Server Authentication or Windows
Authentication: Which One Is
Recommended?
SQLServer2000canberunintwomodes:
N SQLServerauthenticationandWindowsauthentication("mixed
mode)
N Windowsauthenticationonly
AcommondebateisoverwhethertheSQLServershouldbeaccessed
usingSQLServerauthenticationorbyWindowsauthentication.SQL
ServerauthenticationmeansthattheCOM+applicationcanloginassa
(or,preferably,withacustomSQLServeraccount).WithWindows
authentication,userslogintoNT/Windows2000,andthisauthenticationis
thenusedinSQLServeraswell.
It'struethatitissimpletouseSQLServerauthentication.Anybodycan
spellsa andrememberablankpassword.(OK,Iamjokinghere,but
someonepresentedstatisticsthatsaid-ifIremembercorrectly-that
somethinglike30%oftheworld'sSQLServersinproductionuseablank
passwordforsa.)Evenwithapasswordforsa,andwhensa isn'tusedas
thedatabaseuserbythecomponents,SQLServerauthenticationisless
safethanWindowsauthentication.
IfyouuseWindowsauthenticationwithaserver-sideapplication,youdo
nothavetohardcodeapasswordintotheapplication(orkeepthe
passwordelsewhere),andyoucanchangethepasswordwithoutchanging
theapplication.NT/Windows2000passwordsalsohaveaging,whichis
yetanotheradvantage.
Let'sskiptheserverapplicationscenarioforamomentandconsidera
situationforwhenenduserswilllogintoSQLServer.Ifusershaveto
enterapasswordthatisanSQLServerauthenticationpassword,Ithinkit
ismorelikelythatthatpasswordwillbesharedwithfriendsattheoffice
thantheuser'sownaccountforloggingintothenetwork.
Data Tier
Nowthatwe'vediscussedtheConsumerandBusinesstiers,let'smoveontotheDatatier.The
DatatierislocatedatthedatabaseserverandismadeupofthePublicandPrivateStored
Procedureslayers,whichaccesstables,views,andUser-DefinedFunctions(UDFs).
Public Stored Procedures Layer
InthePublicStoredProcedureslayer,youfindyoursmallandcontrolledsetofentrypointstothe
database.Thereisnorealdistinctionbetweenpublicandprivatestoredprocedures,butwiththe
helpofdisciplineandanamingconventionthatsaysthatpublicstoredproceduresshouldstartwith
a_,onlythepublicstoredprocedureswillbecalledbythePersistentAccesslayer.
Note
Ichosea_ astheprefixforpublicstoredproceduressothattheseprocedureswillcome
firstinanorderedlistofallstoredprocedures.Evenmoreimportantisthatthestored
procedureswillbegroupedinorderedlistings.
Sometimes,thepublicstoredproceduresinthislayerwilldotherealworkontheirown,butthey
willusuallyaskforhelpfromprivatestoredprocedures.It'scommontoseealotofduplicatedcode
whenitcomestostoredprocedures.Thisisbecauseitisoftenmuchhardertocallotherstored
procedurescomparedtocallingsubs/functionsinVisualBasic.NET,forexample,andT-SQLputsa
lotofobstaclesintheway.Still,thedamagecausedbyduplicatecodeisasseriousasinanytype
ofprogramming.Makesurethatyoucentralizecodeasusual,anddosobycallingprivatestored
procedures.
Figure5.11showsthethreepublicstoredproceduresthatwilltakecareoftherequestscoming
fromtheconsumer.NotethattheUnifiedModelingLanguage(UML)usedinFigure5.11isn'treally
formodellingstoredprocedures.Still,usingitcanhelp,eventhoughitischeatinginaway.
Figure 5.11. Stored procedures in the Public Stored Procedures layer.
Private Stored Procedures Layer
ThepurposeofthePrivateStoredProcedureslayeristodotherealworkandaccessthetables,
views,andUDFs.Becausethelayerisprivate,exceptforintheeyeofthepreviouslayer,youcan
changetheinterfaceoftheprivatestoredprocedureswithoutaffectingmorethantheclosestlayer.
Thisisoneofthetypicalbenefitsoflayering,butI'mmentioningithereanywaybecauselayering
isnotthatcommonforT-SQLprogramming.
InFigure5.12,youfindthethreeprivatestoredproceduresthatdotherealworkinhelpingthe
publicstoredprocedures.Asyouperhapshaveguessed,thepublicstoredprocedures
a_ErrandFetchBySymbolicName() anda_ErrandFetchByUserAndDates() arebothsolvedwiththe
Errand_FetchByFilter() storedprocedure.Anda_ErrandFetchById() callsboth
Errand_FetchById() andAction_FetchByErrand() toretrieveboththeerranditselfandallthe
actionsforthaterrand.
Figure 5.12. Stored procedure in the Private Stored Procedures layer.
ThePrivateStoredProcedureslayerisanotherlayerthatcouldbeskipped,especiallyinsmall
systems.IntheexamplesshowninFigures5.11and5.12,thereislittlereasontohaveaprivate
layer.Instead,thepublicprocedurescouldcontainallthelogicnecessarytofulfilltherequests.On
theotherhand,itisseldomassimpleasjustasinglefetchofonerowandnothingelseinreal
systems.Thereisoftenaneedforauditing,orperhapstheendusermayonlyseehisownerrands,
andsoon.Addtothisthefactthat fetchinganerrandmustbedonefromseveraldifferentstored
procedures,andthattheprivatestoredprocedurescantypicallyhavemorecomplicatedparameter
liststobemoregeneralandusablefrommanypublicstoredprocedures,andvoila-youhavesome
goodreasonsforsplittingtheprocedureintoapubliconeandaprivateone.
Tables, Views, and User-Defined Functions
Soonerorlater,wewillaccessthetablesinthedatabase.Ifwewanted,wecouldaddonemore
levelintheformofviewsorUDFs.Ifyouusesinglestatementtable-valuedfunctions(oneofthe
threetypesofUDFs),thecodethatwillbeexecutedwillbethesameasifaviewhadbeenused.
However,IstillthinkthatUDFshaveanadvantageoverviews,namelythepossibilityofusing
parameters.
BecauseIusestoredproceduresastheonlyentrypointtothedatabase,viewsandUDFsareless
ofamustthaninothersystems.Still,theycanbeveryhelpfulinthelongrun,forexample,when
(notif!)thereisaneedforschemachange.AvieworaUDFcanoftengracefullyhidethischange
fromtherestofthesystem.Forsimplersystems,IalsoskipviewsandUDFs,atleastassomething
thatshouldbeusedacrosstheboard.Fromtimetotime,thereareproblemsthataresolved
elegantlywitha vieworaUDFinanysystem.
Note
Throughoutallthelayers,Iusedaverybasicexample.Youwillfindmoreexamples
relatingtothearchitectureinthefollowingchapters,andtherearealsomorecode
samplesatthebook'sWebsiteatwww.samspublishing.com.
Communication over the Network to the Application Layer
Atthetimeworkonthisbookbegan,acommonquestionwashowconsumerswouldtalkto
servicedcomponentsoverthenetwork.WithVB6,DCOMwasmostoftenused,butwith.NET,
thereareseveraldifferentmethodsfromwhichtochoose:
N XMLWebservices
N Remoting
N DCOM(asbefore)
Beforewediscusstheseoptionsabitmore,I'dliketogiveaschematicexampleofeachoneinthe
contextofmyproposedarchitecture.
Note
Unfortunately,the.NETcomponentserviceswon'ttraveloverXMLWebservicesand
Remoting.(ItstillworkswhenDCOMisused.)ThemainproblemwiththisisthatCOM+
Role-BasedSecurity(RBS)can'tbeusedbetweenmachines.Inmyopinion,thisisthe
mostseriousproblemwiththeCOM+integrationin.NET.
XMLWebservicesisdesignedforsituationswhenanexternalapplicationisn'tinourcontrol.That
is,astandardizedwaytobuildverylooselycoupledsystems.Figure5.13showstheappearanceof
theBusinesstierwhenitisdeployedasanXMLWebservice.Thereareseveralwaystohavethe
BusinesstierbedeployedasanXMLWebservice,onebeingtouseCOM+1.5.ThentheCOM+
applicationcanbedeployeddirectlyfromtheComponentServicesmanagementconsole.IISmust
beonthemachinetoprovidealistenerwiththecallstotheXMLWebservice.
Figure 5.13. An XML Web service and my proposed architecture.
Note
Inthefuture,thePersistentAccesslayerwillprobablynotonlyfetchdatafromtraditional
datastoressuchasdatabases,filesystems,andsuch,butwillalsofetchdatafromXML
Webservices.LettingtheBusinesstierconsumeexternalXMLWebservicesalsofitsin
nicelywiththeproposedarchitecture.
InthescenarioshowninFigure5.14,aWindowsFormsapplicationistalkingtotheBusinesstier.
ThisisamoretightlycoupledscenariothantheoneshowninFigure5.13.Inthiscase,goodold
DCOMcouldbeused,oryoucoulduseRemoting.InFigure5.14,youcanseethatDCOMand
Remotingaresimilarschematically,butwithDCOM,yougettheproxyandthestubautomatically.
WithRemoting,youhavetowritealisteneronyourownforsomeconfigurations,typicallyasa
WindowsService.
Figure 5.14. A Windows Forms application and the proposed architecture.
Note
Ofcourse,IcouldjustaswellhaveshownXMLWebservicesasthewayfortheWindows
FormsapplicationtotalktotheBusinesstier.
AnothertypicalscenarioiswhenaWebbrowserisusedwithanASP.NET/WebFormsapplication,
butthentheBusinesstierismostoftenputatthesamemachineastheASP.NETapplication.
Otherwise,DCOMorRemotingwillusuallybethechoiceforcommunication,asinFigure5.14.
Figure5.15showsascenarioinwhichtheASP.NETapplicationandtheBusinesstierareputonthe
samemachine.
Figure 5.15. An ASP.NET application and the proposed architecture.
AlthoughRemotingdeservesabooktoitself,I'dliketomentionherethatyoucanconfigure
Remotingin.config files.Inaway,thismakestheConsumerHelperlayeralittlelessimportant
thanitisifyoudecidetoconfigureRemotingincode.Meanwhile,DCOMisconnection-oriented,
quiteheavy,andhas severeproblemstravellingthroughfirewalls.Thesewerethemainreasonsfor
buildingRemotingin.NET.MicrosoftalsotriedhardtocreateRemotingasanextensibleframework
sothatnewchannels,forexample,canbeadded.(Outofthebox,thereisaTCPchannelandan
HTTPchannel.)
AsIsaid,oneunfortunateproblemwithRemotinginthefirstversionof.NETisrelatedtoserviced
components.Componentserviceswon'tflowoverRemoting.Honestly,thisisn'tthatmuchofa
problembecauseyouseldomhaveservicedcomponentsattwodifferentmachinestalkingtoeach
other.(ComponentserviceswillflowoverDCOMasusual.)IfyoudecidetogoforDCOMbetween
managedcomponents,youwilluseinterop,butthisisasmallperformancepenaltytopay
comparedtothenetworkhop.(Remotingisnotonlytobeusedbetweenmachines,butalsofor
callsbetweenAppDomains.)
Asyoucansee,thereareseveralwaystotakecareofcommunicationsbetweentierswhenplaced
atdifferentservers,andnobestpracticeshaveevolvedyet.Onlytimewilltellwhatthebest
choicesforcertainsituationswillbe.Justtobesure,youshouldaddaConsumerHelperlayerthat
willhidemostofthecommunicationdetailsfortheConsumerlayer.
Problems with My Architecture Proposal
Becausethereareseveraldifferentscenariosthatcanbeconsidered,thiscouldpotentiallybea
verylengthysection.Asinotherchapters,attheendofthischapterIwillevaluatemyproposal
basedonthecriteriaIestablishedinChapter2.Fornow,let'stakeaquicklookatsomebasic
problemswithmyproposal:
N Onesizedoesn'tfitall-still,Ifinditconvenienttouseaflexible architectureasastarting
pointandtoadjustitinsteadofstartingfromscratch.Ihaveindicatedintheprevious
sectionswhatlayersyoumaywanttoskipforsmallapplications.
N Becauseofalltheuseofstoredproceduresandtheamountofresponsibilityputonthe
storedprocedures,itwon'tbeeasytoporttheapplicationtoanotherdatabaseplatform.
N Allthoselayersmayprohibittheuseofcustomexceptions.Aconsumerofaclassmust
recognizeanexceptionitreceives.Thismeansthatiftheconsumedassemblyusesanother
assemblywithcustomexceptions,thesecondaryassemblymustbereferencedbythe
consumertoo.Otherwise,ifthesecondaryraisesanexceptionthatthefirstassembly
doesn'tcatch,theconsumercan'tcatchthe"realexception.
New Concepts to Consider When Creating a New Architecture
Today
Asweallknow,ifyoucompareVB6andVisualBasic.NET(orC#)code,you'llfindmany
differences(whenitcomestoservicedcomponents,forexample).Iwilldiscusssomeofthese
differencesbrieflyinthefollowingsectionsaswellasCOM+contexts,someofthenewfeaturesin
SQLServer2000,andsomeunusualdesignideas.IwillstartwithdiscussingConsumers.
New Concepts to Consider Regarding Consumers
Thereisoneimportantconceptthatyouhavetograspforbuildingconsumersforscarceresources.
Servicedcomponentsareanexampleofsucharesource,andtheconceptIamthinkingaboutis
Dispose().Let'slookatthisindetailnow.
Dispose()
It'snotamust,butinmyopinion,forthecurrentversionrecommendedthatyouuseDispose()
whenyouaredonewithanobjectofaservicedcomponent.Thissamerecommendationappliesto
allscarceresources.Ithinkthisisironicwhenyouthinkthatgarbagecollectionshouldreleaseus
fromtheburdenofSet x = Nothing asinVB6andfromRelease() inC++.Instead,wehavethe
newrequirementofcallingDispose() inmanysituations.
Why Do We Have to Call Dispose()?
Atfirst,theneedtocallDispose() puzzledme.Ithoughtthatwithobject
poolingitwasnaturaltohavetocallDispose() sothatusedobjectswere
givenbacktothepoolasfastaspossibleinsteadofwaitingforthe
garbagecollection.Ididn'tunderstandwhyitwaspreferabletouse
Dispose() forallotherservicedcomponents.WhenIdiscussedthiswith
Microsoft,EgidioSburlino,DeveloperEngineerLeadintheCOM+group,
andDaveDriver,DeveloperEngineerintheCOM+group,explainedita
bitmore.Theysaid,"InthecaseofJITactivationwithAutoComplete()
methods,wedo knowwhentocallDispose().Butinallothercases,
whereyoudon'twanttowaitforthegarbagecollectortokickintorelease
yourobject'sresources,youmustcallDispose().Theyalsotoldmethat
currently,ObjectPooling(withoutJITactivationandAutoComplete())is
theonlyfeaturewherecallingDispose() isamust; whenthegarbage
collectorkicksin,itistoolatetorecycletheobjectbecauseofthe
garbagecollectornondeterministicbehaviorwhenitfinalizestheobjects;
i.e.,managedobjectsreferencedbyyourobject'sdatamembersmay
havealreadybeenfinalized.
Iwouldliketoaddthattheconsumershouldn'tandwon'tknowifthe
consumedcomponentispooledorusingJITactivationorwhatever.
Therefore,Dispose() shouldalwaysbeused ifpossible.(Therearesome
caseswhereitisimpossiblefortheclientstoknowwhentocall
Dispose().Thisisthesituationwhenmultipleclientsreferencethesame
object.Inthiscaseitisuptothecomponent/systemdesignertodecideto
letthegarbagecollectionkickintoreleasetheresourceswhenthereare
nomoreclientreferencestotheobjectordesigntheclientinsuchaway
thatitisknownwhoisresponsibleforcallingDispose().Forexample,
Singletonissuchapatternwheremultipleclientssharethesameobject,
withouttheclientshavingknowledgeabouteachother.
AccordingtoothersourcesatMicrosoft,performancewillbeincreased
some,soit'smostlypositivetohaveitasaruletoalwaysuseDispose()
forservicedcomponents.
TheUsing() statementinC#isagoodsolutiontotheproblemofobjectsatthemethodlevel,but
wedon'thaveUsing() inVisualBasic.NET;theclienthastoremembertocallDispose() inthe
Finally block.Furthermore,whenitcomestoinstance-levelobjects,Using() inC#isofnouseif
youwanttheobjecttoliveoverseveralmethods,becauseUsing() hastobeusedatthemethod
level.Toconcludeallthis,nondeterministicfinalizationisarealprobleminsomesituations,
especiallyforthoseconsumersoverwhomyouhavenocontrol.Theservicedcomponentsshould
belessofaproblembecauseyouhavefullcontrolofthese.Remember,too,thatit'susuallynota
catastropheifDispose() isn'tcalled,assoonerorlaterthegarbagecollectorwillkickinandclean
up.
IwillcontinuethisdiscussionaboutDispose() inthe"StandardizedCodeStructureforServiced
ComponentsectionlaterinthechapterwhereIshowcodestructuresforconsumersandserviced
components.
New Concepts to Consider Regarding Serviced Components
Therequirement(oratleastrecommendation)forDispose() wejustdiscussedappliestoserviced
components.AsIsaidbefore,whatIseeastherealproblemistheresponsibilityputonthe
consumerprogrammertocallDispose().Butthereareotherissuestoconsiderregardingserviced
components.We'llstartbylookingatoverloading.
Overloading
ThepossibilityofoverloadinginVisualBasic.NETisgreat.Withitshelp,Icanavoidsomeugly
tricksthatIoftenusedinVB6,suchassendingemptyparametersandtohavedifferentnamesfor
the"samemethodwhenthemethodsjustdifferbytheparameterlists.Thepossibilityof
overloadinghelpstocreateanotherlayerofabstractionsbecauseyoucantalkaboutamethod
first,withoutthinkingthatmuchthattherearereallytendifferentsignaturesforthatverymethod.
YousawexamplesofthatwhenIusedoverloadingearlierinthischapter,suchasinFigure5.5.
Implementation Inheritance
WefinallyhaveimplementationinheritanceinVisualBasic.NET.Microsofthasattemptedtolessen
theproblemswithimplementationinheritance(forexample,withthehelpofshadowing),
sometimessummarizedby theterm"thefragilebaseclassscenario.
10
Still,youcanonlyusesingleinheritance,anditisoftentakenbytheframework,sotosay.One
exampleofasituationinwhichyouareforcedtoinheritfromaspecificclassisserviced
components.If,throughyourCOMhabits,youareaccustomedtousinguser-definedinterfaces
insteadofimplementationinheritance,you'reinluck,becausethatmethodologyfitsperfectlywell
intheworldof.NETtoo.Therearesituationswhenimplementationinheritancecanbeyourfriend,
butit'snotasilverbullet.Itisjustonemoreimportantdesigntoolthathasbeenaddedtoyour
toolbox.ItypicallyuseinheritanceforgeneralizedutilitiesandwhenIwanttoforceapolicyfora
groupofclasses,notforapplication-specificcode.(Imean,Idon'tuseinheritanceforbuilding
hierarchiessuchthatCustomer inheritsfromCompany andsimilarconstructs.)Iwilluseinheritance
so thatIinheritfromServicedComponent toaclasscalled
Jnsk.Enterprise.ServicedComponentHelper.Thenallmyservicedcomponentswillinheritfrom
thehelperclassinstead.Thatway,mostoftheDispose() problemswillbehandledforthe
ordinaryserviced components.ThankstotheTemplateMethodpattern,
8
theclassesinheritingfrom
Jnsk.Enterprise.ServicedComponentHelper willgethelptoimplementaDispose() method.
WhenIdiscussedimplementationinheritancewithJoeLong,theGroupManagerforCOM+at
Microsoft,hetoldmethatanothersubtleproblemwithinheritancecanshowupwiththeusageof
attributes.Forexample,hesaid,abaseclassismarkedas"requirestransactions,andthederived
classismarkedas"transactionsdisabled.Iftheimplementationofapublicmethodinthederived
classcallsonapublicmethodinthebaseclass,atransactiongetsstartedup,whichmaynotbe
obvioustoclientsgiventheattributesonthederivedclass.Idon'tuseanyCOM+-related
attributesonmyServicedComponentHelper.
Object Pooling
IthasbeenpossibletouseobjectpoolingwithCOM+1.0foralongtimenow,butitwasn't
possibleforcomponentswrittenwithVB6.Nowthatproblemhasdisappeared,thanksto.NET,and
weVBprogrammerscanuseobjectpoolingtoo.Youshouldn'tuseobjectpoolingforallofyour
components,butifanyofthefollowingcriteriaapply,itcanbeagoodsolution:
N Thecomponentisexpensivetocreateordestroy,forexample,becauseitneedstograb
someresourcesorcleanupsomeresources.
N Youwanttomultiplexmanyrequeststoacertainnumberofinstances.
N Youexpectextremeloadsoeventhememorymanagerwillhaveatoughtimedoingi tsjob.
Idon'tthinkitisagoodideatoletthecomponentsintheApplicationlayeruseobjectpooling,
mainlybecausecallingDispose() isamustwithpooledobjectsandyoucan'tbesurethe
consumerwillbehavewell.
Note
WewilldiscussobjectpoolingmoreinChapter8.
To JIT or Not to JIT?
Whenitcomestohavingtheneedfordistributedtransactionstakencareofbythe.NETcomponent
services,answeringwhetheryoushoulduseJITactivationiseasy.YoumustuseJITactivationto
usethetransactionservice.Inmostothersituations,youshouldprobablynotuseJITactivation.I
ranintothetrapofusingittoooftenbefore,butasIseeittoday,ithaslittleuseinother
situations.ThefollowingareafewexamplesofsituationsforwhichusingJITactivationisnot
appropriate.
N Forpage-basedconsumers,suchasASP.NET,youwillreleasethereferencewhenthepage
isdone.Ifyouonlyusethereferenceonceduringthepage,thereisoverheadbecauseof
JITactivationthatyouhadnousefor.Ifyouusethereferenceseveraltimesduringthe
page,therewillbeseveralactivations/deactivationswithnouse.It'sbettertokeepthe
referenceforthelifeofthepage.
N ForWindowsFormsconsumers,youwillnormallynotsaveanymemoryattheapplication
serverbyusingJITactivation.AtleastnotiftheJITedobjectisasmallonethatdoesn't
holdontoalotofmemoryorotherexpensiveresources.It'softenbettertokeepthe
referenceforthelifeoftheforminstanceormaybetoinstantiatethecomponenteachtime
youneedit.
Whetheryouchoosetokeepthevariableatinstancelevelormethodlevelisamatterofhowmany
methodcallsyoumake.Forexample,ifaformistocallaninstanceofaservicedcomponentmore
thanonce,keepingthevariableatinstancelevel(andonlyinstantiatingitonceduringthelifetime
ofthecontainerinstance)ispreferableforperformancereasons.Ontheotherhand,ifthereare
minutesormorebetweencalls,itispreferableforscalabilitythatthemethodisdeclaredatmethod
level.
ThereasonfortheperformancebenefitofJITisthattheproxy,thechannel,thestub,andthe
contextarekeptalivebetweenmethodcalls.Thisisactuallythecasewhenyouusetheobjectas
statefultoo,butinthatcase,theobjectisalsokeptalive.
Inhisbook,Transactional COM+: Building Scalable Applications,TimEwald
9
discussesanother
situationwhereJITactivationisusefulwithobjectpooling.WithoutJITactivation,aconsumermay
holdontotheobjectfortoolong,notreleasingitbacktothepool.Iprefernottoletacomponent
fromtheConsumertierholdapooledobjectdirectly,becausetheconsumercomponentmight
behavebadly.Thatis,IseldomuseobjectpoolingfortheApplicationlayer.Forserviced
componentsasconsumers,Icodesothattheresourceisdisposedattheendofthemethod.Ifyou
useobjectpoolingintheApplicationlayer,itmightbeagoodideatouseJIT.
I'vealsoheardthemotivationthatJITisgoodtomovetheresponsibilityofwhenanobjectshould
bedeactivatedfromtheconsumertotheserver.Ifyoudon'tacquireexpensiveresourcesinthe
Applicationlayer,thisisnotavalidreason,inmyopinion,becausetheinstancefromthe
ApplicationlayerisprobablynotmoreexpensivetoholdontothanaJIT-enabledcontext.(Ifthis
isthecase-thatis,theobjecthasalotofdata-youcanalwayssplittheclassintwosothatoneis
heldbytheconsumerandthesecondonehasallthedataandispooled.)
WorthnotingisthatJITprovidesdeterministicfinalization,but,asI'vesaidhere,youhaveto
considerifitisworththeprice.
AnotherandmoresubtleconsiderationregardingJITactivationisthatusingMicrosoft'sComponent
LoadBalancing(CLB)inApplicationCenter2000willhaveimplicationsonloadbalancing.Theload
balancingwillonlytakeplacewhenaninstanceiscreated,notactivated.Thismeansthatwhen
youactivateyourinstancethesecondtime,it'snotatallsurethattheserveristheonewiththe
mostfreecapacity.
Toconcludeallthis,watchthatyoudon'toveruseJITactivation,butmakesureyoutestwhatis
mostefficientforyourspecificsituation.Justasusual.
Contexts
OnereasonfortryingtoavoidJITactivationifpossibleisthatitisinterceptionbasedandrequires
theinstanceofaservicedcomponenttoliveinanondefaultcontext.IfyoucanskipJITactivation,
youareonestepclosertousingco-locationinthecaller'scontextorusingCOM+1.5tousethe
defaultcontextinstead.Bothsettingswillsaveyoualotofmemory,instantiationtime,andcall
overhead.InearliertestsIcarriedout,IhadsimilarthroughputwhenIcomparedunconfigured
COM+componentsandconfiguredco-locatedCOM+components.
Improved Context Activation Setting in
COM+ 1.5
COM+1.5hasanewcontextactivation settingcalled"Mustbeactivated
inthedefaultcontext.Thismeansthattherewillbeno"call
interception,andtheobjectwillbeco-locatedwithseveralotherobjectsin
thedefaultcontext.Thedefaultcontextisaspecificcontextforeach
apartmentthatwillhostallobjectswiththissetting.Itwillalsohostall
legacyunconfiguredCOM+components.
SinceCOM+1.0,wehavehadthechancetousethesetting"Mustbe
activatedincaller'scontext.Thatmeansthattheobjectwillbeco-located
in thecaller'scontextand,therefore,itwillnothaveinterceptiononits
own.Theobjectwillsharetheinterceptorsthatitscallerhad.So,for
example,Icouldbecreatedinmycaller'scontext,andthenmycaller
handsaninterfacepointertosomebodyelse.Igetcalled,mycaller's
contextwillbeentered,andthepropertieswillgettheenter (andleave)
events.Thereisalsonoextracontextsomemoryissaved.Tosuccessfully
co-locateyourobjectinthecaller'scontext,theirsettingsmustbe
compatible.Finally,observethatactivationtimeservices,suchasobject
poolingandobjectconstructorstring,canstillbeusedbecausetheydon't
relyoninterception.
Atthetimeofwriting,therewasnosourceforfurtherreadingon"Mustbe
activated inthedefaultcontext,butTimEwald'sbook,Transactional
COM+: Building Scalable Applications
9
isagoodsourceforreadingmore
aboutcontexts.
Becauseofthecontextissues,IputthecomponentsoftheDomainlayerandPersistentAccess
layerinDLLsoftheirown.ThoseDLLswillthenbeconfiguredinaseparateCOM+library
application.Basically,noserviceswillbeusedforthatapplicationoritscomponents.Allservices
thatrequireinterceptionwillbeusedforthecomponentsintheApplicationlayerinstead.
Finally,theruleofthumbisfirsttoconfigureyourcomponentstousetheservicestheyneed.
Then,forresourcesavingandperformancereasons,ifyouneedinterception,useanondefault
context(whichiswhatyougetautomaticallywhenyouconfigureservicedcomponents)fortheroot
objectandco-locatesecondaryobjectsintherootobject'scontext(orletthemethodsofthe
secondaryobjectsbeShared).Ifyoudon'tneedinterception,usethedefaultcontextforall
objects.
Note
Ifyoudosomesimpleperformancetests,youwillfindthatinstantiationtimefora
componentthatwillco-locateinthecaller'scontextisafractionofacomponentwhose
instancesliveincontextsoftheirown.Atfirst,Ididn'tthinkthisshouldmatterwhenreal
tasksareaddedtothecomponents.However,acoupleofmonthsago,Iwasinvitedto
comeupwithideasataperformance-tuningsessionwhereaspecificusecasewastobe
tuned.Thesinglechangeofstartingtouseco-locationinthecaller'scontextincreased
thethroughputofthereal-worldusecasefourfold.
Onceagain,ifyouuseShared methodsonlyfortheDomainandPersistentAccesslayers,youdon't
havetoworryaboutthecontextissues.
New Concepts to Consider Regarding the Database
Sofar,Ihavediscussedanumberofnewdesignpossibilitiesyouhaveatyourdisposalthanksto
.NET.EventhoughSQLServer2000isn'tthatnew,therearesomeconceptsthatthisversion
makespossible.Inthissection,I'llpresenttheseandafewothertips.
User Defined Functions (UDFs)
AsIsaidearlier,UDFscanbeuseful.Oneexampleisthesingle-statement,table-valuedfunctions
whichwillbeimplementedasVIEWsbehindthescenes,butwithparameterization.Youmay
wonderwhyIprefertousestoredproceduresasentrypointsinsteadofUDFs.Onereasonisthat
therearemanyrestrictionsonwhatisallowedinaUDF.Forexample,youarenotallowedtocalla
storedprocedure.
XML Support
XMLisagreattechnologyinmanysituations.Forexample,itreallyshineswhenyoudon'thave
controloftheconsumer.AnothersituationwhenXMLcanbeusefuliswhenyouarehandingover
muchdatainonesinglecalltoastoredprocedure(whichIwilldiscussfurtherinChapter8,"Data
Access).
Globally Unique Identifiers (GUIDs)
ThepossibilityofnativelyusingGloballyUniqueIDentifiers(GUIDs),orUNIQUE IDENTIFIERsas
SQLServercallsthem,cameinversion7.Ihaveusedtheminacoupleofdatabasedesignsforthe
primarykeys,andIammoreandmorepositiveaboutusingthem.Thereareseveralreasons to
useUNIQUE IDENTIFIERsforprimarykeys:
N Thereis"noriskofoutgrowingthesizeofthedatatype.Thatisactuallyacommon
problemwiththe32-bitINT datatype.
N Youcanreducethenumberofroundtrips.Theconsumercancreatethecompletestructure
ofseveralmasterrowsandtheirdetailrowsandthenimmediatelysendoverallthat
informationtotheapplicationserverforforwardingtothedatabaseserver.Therewon'tbe
anyproblemswithduplicatekeys.(Therecouldbeproblemstheoretically,butnot
practically.)
ThemosttypicalsolutiontocreatingaGUIDistouseGuid.NewGuid() (ortousetheCryptoAPI)
inVisualBasic.NETcodeandtouseNEWID() inT-SQL.
N AnewGUIDisnotonlyuniquepertable,but,mostoften,itisalsouniqueperdatabase.In
somesituationsandsystems,thiscanbearealadvantage.
N HavingaGUIDastheIDforalltablesisconsistentandmakestheprogrammingofthe
componentlayerseasier.Youwillfindthatyoucanmanagewithfeweruser-defined
interfaces,forexample.
N IfyouwanttousemergereplicationinSQLServer7/2000,youmusthaveaUNIQUE
IDENTIFIER ineverytabletobemergereplicated.Thenwhynotuseitastheprimarykey
becauseit'sthereanyway?
N AGUIDwillnotmeananythingatalltotheusers.AnINTEGER canberead,understood,and
remembered.It'scommontoseeINTEGER keysintheUI.
N Youknowwhatanightmareitcanbe,makingamanualmergebetweentwotables,both
withINTEGERsasprimarykeys.Notonlydoyouhavetocreateanewsequenceforthe
unionofbothtables,youmustalsochangealltheforeignkeysforallthedependenttables.
IfGUIDshavebeenused,thissortofproblemdoesnotarise.
Obviously,therearealsodrawbackstousingUNIQUE IDENTIFIERsforprimarykeys:
N Becauseitis16bytes,theGUIDisfourtimesthesizeoftheordinaryINT.Forindextrees,
thiscanleadtomuchworseperformance.Ontheotherhand,thedatabasedoesn'thaveto
maintainasequenceonitsown.
N It'shopelesstoworkmanuallywithGUIDsintheenterprisemanager,forexample,andto
manuallyrelaterowsindifferenttables.ByusingSQL,thisisnotaproblem.
N Relationaldatabasepuristsdon'tthinkit'sagoodideatousesynthetickeys.However,this
isnotaproblemforGUIDsalone,morefortheideaofhavingakeythatisnotvaluebased.
Note
Thereareadvantagesanddisadvantageswitheverything,butinthelastfewyearsIhave
grownmoreandmorefondofusingobjectidentitystylekeys,orsurrogateorsynthetic
keys-whateveryouprefertocallthem.
Whenversion1of.NEThasshipped,youwillfindtheresultsofsomeperformancetestsregarding
UNIQUE IDENTIFIERsasprimarykeysatthebook'sWebsite,atwww.samspublishing.com.
Databases as Components
Forsometimenow,IhavehadwhatIthoughtwasawildidea-tothinkintermsofcomponents
regardingthedatabase.Thatway,thedatabasewouldbesplitintoseveraldatabases.Let'slookat
anexampleofhowthiscouldbeapplied.AtypicalEnterpriseResourcePlanning(ERP)system
containsahugenumberofsubsystems,suchasorders,shipping,complaints,andsoon.Ifyou
applythecomponentconcepttothesedatabases,therewouldbeonedatabaseforeachofthose
subsystems.When thesystemgrows,itiseasytomoveonedatabasetoaseparateserver,which
isscalable.
JusttheotherdayIlearnedthatperhapsthisisn'tsuchawildideaafterall.Infact,itisdiscussed
agreatdealinPeterHerzumandOliverSims'sbook,Business Component Factory.
11
Unfortunately,HerzumandSimsdon'tsolvewhatIthinkofasthemainproblem,namelythe
problemwithforeignkeysbetweenthedatabasesbecausethatcan'tbetakencareofautomatically
withthebuilt-inpossibilitiesofdeclaringforeignkeysinSQLServer.
Havingdatabasesascomponentsisactuallyawaytoprovidephysicalpartitioningofthedatabase.
Physical Partitioning
InFigure5.3,yousawthattypicalphysicalsplittingisdonebetweentheConsumerHelperlayer
andtheApplicationlayer,andbetweenthePersistentAccesslayerandthePublicStored
Procedureslayer.IfthefirsttierisWebForms,combiningtieroneandtwoisrecommended.For
smallsites,it'scommontocombinetier2and3(regardlessofwhethertheconsumertypeisWeb
Formsorwhatever).Butwhatifyouwanttohavemorethanonemachineforatier?Let's
investigatethis,startingwiththeservicedcomponents.
Physical Partitioning of Serviced Components
It'spreferabletocloneinsteadofphysicallypartitionyourservicedcomponents.Thisisespecially
trueifthecloningorpartitioningistobedoneforperformanceorscalabilityreasons.Ifthereare
otherreasons,forexample,thatsomeprocessingmustbeseparatefromtheotherforsecurity
reasons,youmaynothaveachoice.
Ifpartitioningisneededwithinatier,betweenwhichlayersshoulditbedone?Thereisnostandard
answertothatquestion.Iprefertofactoroutwhatmustbedoneatanotherserver,andcarefully
tunethecallstominimizethenumberofroundtrips.
Physical Partitioning of the Database
Beforeversion2000ofSQLServer,itwasnotcommontopartitiontablesinOn-LineTransaction
Processing(OLTP)systemsinthatenvironment.Whenpartitioningwasused,itwasmostlydone
forcompletetablesorforreasonsotherthanloadbalancing.HereI'mgoingtodiscussanew
solutionforpartitioninganSQLServer2000databasecalledDistributedPartitionedView(DPV).
Afterwards,Iwillbrieflydiscusshowyoucanbuildasimilarsolutiononyourownwithany
databaseproduct.
Distributed Partitioned Views (DPV)
DPVmadeitsothatiswasnolongerdifficulttoscaleoutthedatabase.Microsofthasprovedwith
theTPC-CresultsthepossibilityofrawscalabilitythankstoDPV.Evenso,itisprettyeasytoget
startedusingDPV.Themainphilosophyistocreatethesametableinseveraldifferentdatabases
(atdifferentdatabaseservers),but withdifferentmutuallyexclusiveconstraints.Thenaviewis
createdthatusesUNION tocombinetherowsfromeachtableintoavirtualtableateachserver.To
theconsumerofthedatabase,itistotallytransparentatwhatserverthedataisreallylocated.The
consumercantalktoanyofthedatabaseserversandgetanyoftherows.
Ofcourse,itwillbemostefficientiftheconsumerhitsthecorrectdatabaseserverwithhisorher
request.Therefore,agreattipistousearoutingtrickinthePersistentAccesslayertogotothe
rightdatabase,dependingontheIDoftherowtofetch,forexample.Ifyouwantrow100200,you
shouldgotoserverAbecauseithasrows1to200000.Youcangotoanydatabaseserver,butit
willbefastesttogotothecorrectone.
Unfortunately,thetechnologyisn'tmatureyetandtheadministrativesupportisnotstrong.Other
drawbacksarethatyoumusthavetheSQLServerEnterpriseEditionforeachserver,andthatis
notcheap.(Ontheotherhand,insituationswhenweneedDPV,thepriceforafewSQLServer
EnterpriseEditionlicensesisusuallynotarealproblem.)Youwillalsohavetomakedesign
preparationsforusingDPV.Forexample,youarenotallowedtohavecolumnsofdatatype
ROWVERSION (TIMESTAMP)intablesthatwillbepartitioned.
Custom Solution
Ifyouaddaroutingtricktoyourcomponents,thenextstepistobuildaDPV-likesolutionyourself.
YoucandomostofitwithoutSQLServerEnterpriseEditionandwithoutthedesignconstraints.
Whatyou probablycan'tdoisprovidethetransparencytodatabaseconsumers,butifallrequests
arethroughtheservicedcomponents,thatshouldn'tbeaproblem.
Note
PleasenotethatIamnotrecommendingthatyoubuildacustomsolution.Don't
underestimatethework-noworinthefuture.Myguessisthatitwillbemucheasierto
administrateDPVinacomingversion.
Proposals for Standardized Code Structures
BoththeVisualStudio.NETIDEandSQLServer2000'sSQLQueryAnalyzerprovidesupportfor
templates. ThesearegreatfeaturesthatIusealottogetstartingpointsformycomponentsand
storedprocedures.Whyshouldstandardizedcodestructuresbeused?
N Inmyopinion,it'smucheasiertomaintainabunchofstoredproceduresthatsharea
commonstructurethanifeachofthemhasaspecificone.Thisgoesforcomponentstoo.Of
course,youmustbeflexibletohandlevarioussituations,butit'sgoodtohavesomethingto
useasaruleofthumbandasastartingpoint.
N Somepartsarequitedifficulttocodebyhand,andinaccuracieswilloccurfromtimeto
time.Atypicalexampleofthisistheerrorhandlinginthestoredprocedures.
N Sometasksshouldbedoneineverymethodandstoredproceduretomakethetotal
architecturework.Oneexampleofthisisusingtracecallswhenenteringandexitingthe
method/storedprocedure.Anotherexampleisexaminingifthereisanexternaltransaction
activebeforestartinganewoneinthestoredprocedures.
I'mgoingtoshowyoumyproposalsforstandardizedcodestructuresshortly.Youdon'thaveto
likeallofthecodeinmyproposedcodestructures.Imyselflovelookingatotherdevelopers'code
becauseIgetinspirationabouthowtorefinemyown.Perhapsyouwillgetoneortwosuchideas
yourselfbylookingatmycode.
Note
IwilldiscusshowtotakecareoftransactionsinmoredepthinChapter6.
Somedeveloperswouldarguethatitisbettertothinkthantojustcopyandpastecode.Itotally
agree,butsomepartsjustdon'thavetobethoughtabouteachandeverytimeanewmethodis
needed.Another wayoflookingatitisthatinsteadofthinkingaboutmattersfromscratch,provide
astartingpointwithatemplateandstartthethinkingatahigherlevel.
Ofcourse,therearemanyfactorstoconsiderinhowthetypicalcodeshouldlook.Iwillwalkyou
throughoneexampleforaconsumer,oneforaconsumerhelperanditssuperclass,onefora
servicedcomponent,andoneforastoredprocedure.Ontheway,Iwilldiscussanumberof
aspects.
Typical Code Structure for Consumer to Serviced Component
Insteadof"StandardizedCodeStructureIhavewritten"TypicalCodeStructureinthisheader.
ThisdiffersfromthesectionswhereIdiscusscodestructureforservicedcomponentandstored
procedure.Thereasonforthisisthatthebookisn'tfocusingontheconsumer.Ijustwantto
provideanexampleoftypicalcodetogiveyouabetterfeelingoftheoverallpicture.
Whenyouhavesomegoodcodestructuresthatyouarehappywith,youshouldsavethemtobe
usedastemplates,bothforVisualBasic.NETandforT-SQL.
Justonemorethingbeforewestart.YouwillfindthatIdecidedtocutthecodeintopiecessothat
Icommentpiecebypiece.Ifyouwanttobrowseallthecodeatonce,downloaditfromthebook's
Websiteatwww.samspublishing.com.
The Consumer
Theconsumercouldbe,forinstance,aWebForm,anXMLWebservice,oraWindowsForm.Ihave
useda WindowsFormheretoshowonespecificpoint,namelythattheconsumerhelperinstanceis
heldoveralongerperiodoftime.InthecaseofaWebForm,forexample,theinstancewillonlybe
keptforthelifeofthepagegeneration.
The Consumer Code Section by Section
InListing5.1,youcanseethatIkeeptheconsumerhelperinstanceatinstancelevelfortheform.
Listing 5.1 Instantiation of the Consumer Helper
Public Class frmErrandBrowsing
Inherits System.Windows.Forms.Form
Private m ErrandBrowsingHelper _
As New Acme.HelpDesk.ConsumerHelper.ErrandBrowsingHelper()
It'simportantthattheinstancegetsaDispose() callwhenit'snotgoingtobeusedanymore.Of
course,thegarbagecollectorwillcleanuptheinstance,butitcantakealongtimebeforethat
happensanduntilthen,resourcesattheserver-sideareoccupied.InListing5.2,youcanseethat
IcallDispose() intheOnClosed() eventfortheform.(I'mnotshowingtherestofthelogicfor
theform,wherethemethodsoftheconsumerhelperarecalledbecauseitisnotimportanttoour
discussion.)
Listing 5.2 The OnClosed Event for the Form
Protected Overrides Sub OnClosed _
(ByVal e As System.EventArgs)
m_ErrandBrowsingHelper.Dispose()
End Sub
'...and a lot of presentation logic.
End Class
YoucanalsoregistertheobjectintheformwithComponents.Add(m_ErrandBrowsingHelper) so
thattheformwilldealwithcallingDispose().
Welcome to the World of Nondeterministic
Finalization
Myfriend,FrancescoBalena,authorofProgramming Microsoft Visual Basic
6.0 andtheupcomingProgramming Visual Basic .NET,andIdiscussedthe
issueofbuildingsomethingsimilartoasmartpointerin.NETtohelpwith
automaticallycallingDispose() whenthesmartpointergoesoutof
scope.Hetoldmethatasfarasheknows,thereisabsolutelynowayof
implementingasmartpointerin.NET,regardlessofthelanguage.He
said:"TheUsing() statementisnothingbutashortcutforenclosingan
objectcreationinaTry...End Try block,andhavingtheFinally block
automaticallycallDispose() iftheobjectexposestheIDisposable
interface.YoucanimplementitmanuallyinVB,butitdoesn'tsolvethe
problem.
Torecap,.NETprogrammingrequiresalotmorecollaborationwithclients
toworkcorrectly-ofcourse,youcanalwaysrelyonFinalize(),butit
isn'tcalledimmediately aftertheobjectislogicallydestroyed,anditalso
slowsdownperformancebecausefinalizedobjectsrequireonemore
garbagecollectiontobereclaimedcompletely.
Finally,Francescosays,youcanonlystateafewguidelinesfor
programmerswhousetheobject,suchas
N AlwaysbracketcodeinsideaTry block,andcallDispose() fromthe
Finally clause.
N Preferclass-leveltoprocedure-levelobjectsifpossible,becausethe
lifetimeoftheformercanbecontrolledmoreeasily.
N ImplementIDisposable andcallGC.SuppressFinalize() inthe
Dispose() method.
N AdheretotheprogrammingguidelinesthatsaythatDispose()
shouldbecallablemultipletimeswithoutany errornorside-effect.
(Thiscanhappenwhenmultipleclientshaveareferencetothe
sameobject.)
N ImplementFinalize() asalastresort.
N ManuallyrunaGC.Collect() toforcefinalizationofpending
objects,butdoitonlywhentheapplicationisidle(somethingrare
inaserver-sidecomponent).
The Consumer Helper
Theconsumerhelperismuchmoreinterestingthantheconsumeritself,andthisisbydesign.The
morethatcanbetakencareofbythehelper,thebetter.
The Consumer Helper Code Section by Section
InListing5.3,youcanseehowthehelperclassisdeclaredandthatitinheritsfrom
Jnsk.Consumer.ConsumerHelper.Inheritanceisgreatforforcingaprotocolonthesubclasses,
suchaswhentheTemplateMethodpattern
8
isused.Withthehelpofinheritancehere,Igetmost
ofthehelpwithIDisposable automatically.
Listing 5.3 The Class Declaration, a Constant, and Some Variables
Public Class ErrandBrowsingHelper
Inherits Jnsk.Consumer.ConsumerHelper
Private Const theTypeName As String = "ErrandBrowsingHelper"
Private m ErrandBrowsing _
As New Acme.HelpDesk.Application.ErrandBrowsing()
InListing5.3,youcanalsoseethatIkeepthenameoftheclassinahard-codedconstant.Icould
useTypeName(Me) insteadtogetthatinformation,butthisismoreefficientanditalsohelpsme
skipacoupleofstringvariables,asyou'llseesoon.ThenIinstantiatetheclassintheApplication
layerandI keepthereferenceatinstancelevel.
IsaidthatIgetmostoftheIDisposable logicautomatically,thankstoderivingfrom
Jnsk.Consumer.ConsumerHelper,butIhavetoprovideacustomimplementationofthe
Dispose(Boolean) method.Thesuperclassdoesn'tknowwhatresourcestodispose.InListing
5.4,youcanseethecustomDispose() method.
Listing 5.4 The Custom Implementation of Dispose(Boolean)
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
m_ErrandBrowsing.Dispose()
Else
'If this method was called from the finalizer,
'that is an "error".
Jnsk.Instrumentation.Assert.Assert _
(False, "Not disposed.", _
AssemblyGlobal.ExeOrDllName, _
theTypeName, False)
End If
End Sub
IfDispose() hasbeencalledbyafinalizer,thedisposingparameterisFalse.Ontheotherhand,if
theconsumerhascalledDispose(),thedisposingparameterisTrue.Iusethatinformationfor
addinganassertionsothatIwillnoticeifaconsumerisn'ttakingcareofDispose() asplanned.
Thebrokenassertionwon'ttellwhotheconsumeris,butIhaveatleastanoticethatthereisa
faultyconsumerandIwillprobablyalsogetsomemorecluesoutofthebrokenassertions.If
disposing isFalse,theconsumerhasabugbecauseitdidn'tcallDispose() explicitly.Dispose()
willbecalledsoonerorlaterautomaticallywiththehelpofthegarbagecollection,butbecauseI
prefersoonerforconsumerstoservicedcomponents,thisistobeconsideredabug.
NotethatthelastparametertotheAssert() methodisFalse forraiseException.Idon'twantto
raiseanexceptioninthiscase.Raisinganexceptionwouldbetooviolent.
Next,it'stimefortheFetch() method,thefirstpartofwhichisfoundinListing5.5.First,a
constantisgiventhenamefortheclassandthehard-codednameofthefunction.ThenIcheck
thatI'mnottryingtouseanobjectthathasbeendisposedof.Ifso,anexceptionshouldberaised.
Listing 5.5 First Part of the Fetch() Method
Public Function Fetch(ByVal id As Guid) As DataSet
Const theSource As String = _
theTypeName & ".Fetch"
If Disposed Then
Throw New _
ObjectDisposedException(theSource)
End If
TheFetch() methodcontinuesinListing5.6withmakingatracecall,whichsaysthatthemethod
isstarting.ThenaTry blockstartsinwhichtheApplicationlayerwillbecalled,andtheresultof
thatmethodwillbereturned.
Note
Intherealversionofthismethod,thereareacoupleofCatch blocksaswell.Iwill
discusstheseindetailinChapter9,"ErrorHandlingandConcurrencyControl.
Listing 5.6 Second Part of the Fetch() Method
Jnsk.Instrumentation.Trace.TraceBegin
(AssemblyGlobal.ExeOrDllName, theSource)
Try
Return m_ErrandBrowsing.Fetch(id)
'I'll leave the discussion about Catch
'for Chapter9.
Finally(asyoucanseeinListing5.7),themethodendswithaFinally blockwhereanewtrace
callismade.It'squitecommontoseeaDispose() callintheFinally blockbut,inthislisting,I
havekepttheresourcethatshouldbedisposedofattheinstancelevel,notatthemethodlevel.
Listing 5.7 Third Part of the Fetch() Method
Finally
Jnsk.Instrumentation.Trace.TraceEnd _
(AssemblyGlobal.ExeOrDllName, theSource)
End Try
End Function
End Class
I'msureyouarewonderingwhatthecodeforConsumerHelper lookslike,solet'stakealookat
thatcodetoo,beforewemoveontotheservicedcomponents.
InListing5.8,youcanseethatIuseMustInherit becauseit'snotinterestingtoinstantiatethe
ConsumerHelper class.IalsoimplementIDisposable inthisclassandIkeepaflagindicating
whethertheinstancehasbeendisposedalready.
Listing 5.8 First Part of the ConsumerHelper Class
Namespace Jnsk.Consumer
Public MustInherit Class ConsumerHelper
Implements IDisposable
Private m_Disposed As Boolean = False
Protected ReadOnly Property Disposed() As Bollean
Get
Return m_Disposed
End Get
End Property
TheimplementationoftheIDisposable interfaceisstraightforward.Theonlypartthatisstrange
isthatDispose(True) iscalled.Thankstothat,thecallwillgotothesubclass,whichmightbe
interestedindisposingsomeresources,asshowninListing5.9.
Listing 5.9 Second Part of the ConsumerHelper Class
Public Sub Dispose() Implements IDisposable.Dispose
If Not m_Disposed Then
Dispose(True)
m_Disposed = True
GC.SuppressFinalize(Me)
End If
End Sub
Protected MustOverride Sub _
Dispose(ByVal disposing As Boolean)
Finally,theFinalize() isoverriddentooand,eveninthiscase,Dispose() ofthesubclassis
called,asshowninListing5.10.
Listing 5.10 Third Part of the ConsumerHelper Class
Protected Overrides Sub Finalize()
If Not m_Disposed then
Dispose(False)
m_Disposed = True
End If
End Sub
End Class
End Namespace
Standardized Code Structure for Serviced Component
Thecodeforatypicalservicedcomponentisquitesimilartothatoftheconsumerhelper.Iwillonly
pointoutthedifferencesandnotrepeatwhatwassaidintheprevioussection.
Serviced Component Code Section by Section
InListing5.11,youcanseethatIuseanImports statementtosavemyselfsomeworkwhen
writingalltheEnterpriseServices-relatedattributes.Then,fortheclass,awholebunchof
attributesareset.(Youonlyneedtowriteanattributewhenaserviceisneeded.Ihaveadded
moreattributesherejustforthesakeofexample.)
Listing 5.11 Multiple EnterpriseServices Attributes for the Class
Imports System.EnterpriseServices
Namespace Acme.HelpDesk.Application
<Transaction(TransactionOption.Disabled), _
JustInTimeActivation(False), _
Synchronization(SynchronizationOption.Disabled), _
ComponentAccessControl(True), _
ConstructionEnabled(False), _
InterfaceQueuing(False), _
EventTrackingEnabled(True), _
MustRunInClientContext(False)> _
ThefirstthreeattributesshowninListing5.11(Transaction(),JustInTimeActivation(),and
Synchronization())willbediscussedinlaterchapters.
Note
Someattributes,suchassizeofthepoolandvaluefortheconstructionstring,for
servicedcomponentswillonlyprovidedefaultvaluesthatcanbechangedatdeployment
time.Thisisdifferentfromotherattributesin.NETandmeansthatanattributesetting
foundinthemanifestisn'talwayscorrect.I'veheardrumorsthatthiswillchangeinthe
futuresothatsomeofthevaluessetincodewon'tbeabletobechangedbythe
administrator.Thatisabetterapproachandwillsavesomeruntimeproblems.Many
COM+long-timerscantellyouawarstoryortwoaboutthetimea"smartadministrator
changedanattributeforacoupleofcomponentstoincreasethroughput.
OtherandJustInTimeActivation(),willnotbeaffectedifyouchangethevalues inthe
CSEtool.Themetadataintheassemblywillrule.
ForthecomponentsintheApplicationlayer,it'scommontohaveComponentAccessControl() on.
Thisdoesprohibityoufromlocatingtheobjectwiththecaller'scontextbut,becausethecalleris
mostoftenatanothertier,itisn'tarealproblem.
Idon'tusuallyuseConstructionEnabled() andInterfaceQueuing().IfIuseaCOM+Server
applicationandforsomereasoncan'tlocatemyobjectinthecaller'scontext(orusetheDefault
contextforCOM+1.5),IsetEventTrackingEnabled() on.Thatway,Icanseehowmany
consumersIhaveatanygivenpointintime,forexample.Finally,inthiscase(becauseof
ComponentAccessControl())Ican'tsetMustRunInClientContext() on.Thisattributeisusually
set onforcomponentsintheDomainandPersistentAccesslayers.
Note
Often,theassemblyfortheApplicationlayershouldhavetheApplicationActivation
attributesettoActivationOption.Server.TheassemblyfortheDomainandPersistent
AccesslayersshouldhaveitsettoActivationOption.Library.
Some Attributes to Think About If You Are
Using the Components from Unmanaged
Code
InSystem.Runtime.InteropServices,therearesomeattributesI
recommendyouusewithyourclassesandinterfacestopreparethem for
beingusedbyVB6consumers,say.TheattributesI'mthinkingaboutare
ClassInterface(ClassInterfaceType.None) and
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown).
YoushouldalsoaddProgIdandGUIDattributesforyourassemblies,
interfaces,andclasses.
ClassInterface(ClassInterfaceType.None) meansthatamanaged
classwillbeexposedonlythroughitsimplementeduser-defined
interfaces.
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)
meansthatyourmanagedinterfacewill beexposedasanIUnknown-based
COMinterface.Ifyoutreatyourmanagedinterfacesasimmutable,this
willversionjustfineforunmanagedclientstoo.
Unfortunately,ClassInterface(ClassInterfaceType.AutoDual) will
giveyouversioningtroublebecausethereisnothingtopreventyoufrom
addingmethodstoyourclass,forexample,andthatwillbreaktheearly-
boundunmanagedclient.Asamatteroffact,ifyouwanttobeabletocall
yourApplicationlayerfromunmanagedclients,thisversioningaspectis a
goodreasonforgivingeachclassintheApplicationlayerauser-defined
interfaceinstead(eventhoughIsaidearlierthatIusuallyprefernottodo
that)anduseNone insteadofAutoDual fortheClassInterface attribute.
Theclassdeclaration(showninListing5.12)startsinasimilarwayastheconsumerhelper(see
Listing5.13).Theonlyrealdifferenceisthattheservicedcomponentinheritsfrom
ServicedComponentHelper andthatthisclassdoesn'tholdanotherinstanceattheinstancelevel.
Listing 5.12 The Class Declaration and a Constant
Public Class ErrandBrowsing
Inherits Jnsk.Enterprise.ServicedComponentHelper
Private Const theTypeName As String = "ErrandBrowsing"
Thesimilaritybetweentheconsumerhelperandtheservicedcomponentcontinuesforthecustom
implementationofDispose(Boolean) asshowninListing5.13.Thereisn'tanythingnewIneedto
addhere.
Listing 5.13 The Custom Implementation of Dispose(Boolean)
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'Dispose resources here.
'None in this example.
If not disposing then
'If this method was called from the finalizer,
'that is an "error".
Jnsk.Instrumentation.ServicedAssert.Assert _
(False, "Not disposed.", _
AssemblyGlobal.ExeOrDllName, _
theTypeName, False)
End If
End Sub
InListing5.14,thefirstpartoftheFetch() methodisshown.Here,youcanseethatIsetthe
AutoComplete() attributeoff.(Iwilldiscussthatattributealotmoreinthenextchapter.)Ikeepa
constantwiththenameofthemethodasusual.ThistimeIdeclarethevariableatthemethod
levelandIdeclarethevariableofaninterfacethatthePersistentAccesslayerclassimplements.
WhenyouinheritfromServicedComponent,youdon'tneedtocheckifanobjecthasbeendisposed
yet,becauseServicedComponent willtakecareofthatforyou.
Note
IfthemethodsinthePersistentAccesslayerareShared,youwon'tdeclarethemand
instantiatethem,butjustcalltheirmethodsdirectly.
Listing 5.14 First Part of the Fetch() Method
<AutoComplete(False)> _
Public Function Fetch(ByVal id As Guid) As DataSet
Const theSource As String = _
theTypeName & ".Fetch"
Dim aFetcher As Acme.HelpDesk.Interfaces.IFetchableById
Afterthat,Istartwithatracecall,asyoucanseeinthesecondpartoftheFetch() methodshown
inListing5.15.ThistimethetracecallismadetoJnsk.Instrumentation.ServicedTrace instead
oftoJnsk.Instrumentation.Trace.Becauseofthis,somemoreinformation(fromContextUtil,
forinstance)willbecollectedandusedinthetracecall.Themethodthencontinueswiththe
instantiationofthecomponentwhilethePersistentAccesslayertakesplace.Afterthat,thereisa
Try blockinwhichtheresourceisused.
Listing 5.15 Second Part of the Fetch() Method
Jnsk.Instrumentation. _
ServicedTrace.TraceBegin _
(AssemblyGlobal.ExeOrDllName, theSource)
aFetcher = New _
Acme.HelpDesk.PersistentAccess.paErrand()
Try
'Call methods on the aFetcher instance
'More about this in Chapter8.
'I'll leave the discussion about Catch()
'for chapter 9.
Note
TheideabehindmyuseofstructuredexceptionhandlingistouseaTry blockforwhen
theresourceisused.TheDispose() callfortheresourcewillbemadeintheFinally
block.Iftheinstantiationfails,thereisnoresourcetoDispose().Thisnestingwillbe
severallevelsformorecomplicatedmethods.(YoucanalsoaddanouterTry block
aroundtheinstantiation.)
WhentheUsing() statementinC#isused,itisactuallyexpandedtoaTry-Finally
construction,exactlyastheonediscussedhere.
Finally,themethodendswithaFinally block,asshowninListing5.16.Here,theresourceis
disposedwithhelpfromDisposeHelper(),whichcaststheobjectIDisposable.Then,another
tracecallismadeheretosignalthatthemethodisended.
Listing 5.16 Third Part of the Fetch() Method
Finally
Jnsk.Div.DisposeHelper(CType(aReader, Object))
Jnsk.Instrumentation.ServicedTrace.TraceEnd _
(AssemblyGlobal.ExeOrDllName, theSource)
End Try
End Function
'The two other methods are not shown here.
End Class
End Namespace
Using IDisposable Through ServicedComponent
BecauseaReader inListing5.13isaninstanceofaclassthatisderived
fromServicedComponent (which,initsturn,implementsIDisposable),
thecasttoIDisposable ispossibleinDisposeHelper().Icouldhave
madethe casttoServicedComponent insteadofIDisposable.One
reasonIchosetodoitlikethisisbecausetherewouldbefewerchangesif
forsomereasontheobjectwerenottobederivedfrom
ServicedComponent inthefuture.
IcouldletIFetchableById andallsimilarinterfacesinheritfrom
IDisposable tomakeiteasierfortheconsumeroftheinterface,butI
prefertonotdothatbecausetheinstancealreadyimplements
IDisposable anditfeelsliketherearealotofimplicationsforsucha
shortshortcut.(TheDisposeHelper() makesthisano-taskanyway.)
Ontheotherhand,whenIworkwithinstancesdirectlythroughtheir
classes(withoutdeclaringtheobjectasaninterface),Idon'thavetodo
thecast.ThiswasshowninListing 5.4.
Finally,beforewemoveovertodiscussingthecodestructureforstoredprocedures,I'dliketo
mentionthatthecodefortheServicedComponentHelper issimilartothecodeforthe
ConsumerHelper.ThekeydifferenceisthatServicedComponentHelper inheritsfrom
ServicedComponent.
Standardized Code Structure for Stored Procedures
Inaway,itfeelsevenmoreimportanttoshowastandardizedcodestructureforstoredprocedures
becauselittlehasbeenwrittenaboutthisanditishardertogetitright.
Note
Notethatusingastandardizedcodestructureisn'tthatimportantforutilities!Therefore,
you'llfindthatIoftenskipitinthatkindofcode.See,forexample,
JnskAssert_Assert() inChapter3.
Standardized Stored Procedure Code Section by Section
Let'slookatatypicalstoredprocedure.First,thenameforthestoredprocedureandits
parametersisgiven.YoucanseeanexampleofthatinListing5.14.Asyoucansee,Iusea"class
orinterface-methodstylewhenIgivethenamesofmystoredprocedures,whichgivesauseful
categorizationintheT-SQLtools.(Forasmallsystem,youoftenendupwithhundredsofstored
procedures.Itcanbeamesstonavigateamongthem.Inalargesystem,it'sanevenlarger
mess.)
YoucanalsoseethatIoftenuseuser-defineddatatypes(UDDTs)fordomain-specificdata.In
doingthis,itiseasierformetoswitchthelengthofalldescriptions,say,ortochangetheinteger
typeforatypeofcolumn,andsoon.IntheexampleinListing5.17,theuddtId isactuallya
UNIQUE IDENTIFIER.
Listing 5.17 Name for Stored Procedure and Its Parameters
CREATE PROCEDURE a_Errand_FetchById
(@id uddtId)
AS
Allstoredprocedureswillbestartedwithasectionofstandardizeddeclarationsandinitializations,
asshowninListing5.18.(It'snotalwayssothatallvariablesareneeded,inwhichcase,ofcourse,
theyarenotused.)Afterthedeclarations,IwilluseSET NOCOUNT ON becauseIdon'tneedtosend
backtotheclientinformationabouthowmanyrowswereaffectedforeachstatementinthestored
procedure.(Inthepast,notusingthisclausealsoledtotroublewithOLEDBdrivers.)
Thenameof thestoredprocedurecouldbefetchedwithOBJECT_NAME(@@PROCID),butIhardcode
itinsteadtosavesomeresources.(Naturally,thischangecanalsobedonewithautility,before
deployment.)Finally,thereisatracecall,signalingthatthestoredprocedureisstarting.
Listing 5.18 Standardized Declarations and Initializations
DECLARE @theSource uddtSource
, @anError INT
, @anErrorMessage uddtErrorMessage
, @aReturnValue INT
, @theTranCountAtEntry INT
SET NOCOUNT ON
SET @anError = 0
SET @anErrorMessage = ''
SET @theSource = 'a_Errand_FetchById'
EXEC JnskTrace_Begin @theSource
-------------------------------------------------------
AsyoucanseeinListing5.19,therearenospecificdeclarationsorinitializationsinthisexample;
otherwise,theywouldhavebeenseparatefromthegeneralonesfoundinListing5.18.
Listing 5.19 Specific Declarations and Initializations
--Any specific declarations or initializations?
Bynow,I'msureyouknowthatIwillbefocusingontransactionsinthenextchapter.Therefore,I
justaskyoutobepatientandIwillexplainthecodeinListing5.20there.
Listing 5.20 Decide Whether a Transaction Should Be Started
SET @theTranCountAtEntry = @@TRANCOUNT
IF @theTrancountAtEntry = 0 BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
END
Note
Asyouknowbynow,Ilovetogeneralizecodeintosmallmethods/procedures.Therefore,
IhavetriedtodothiswiththecodeinListing5.20andwiththeendoftransactionblock
inListing5.22aswell.Unfortunately,generalizingthisisnotpossiblebecauseSQLServer
keepstrackthat@@TRANCOUNT hasthesamevaluewhenastoredprocedureisstartedas
ithaswhenithasended.
InListing5.21,youcanseeanexampleofwhentwosecondarystoredproceduresarecal led.The
firstwillfetchtheerrandandthesecondwillfetchalltheactionrowsfortheerrand.
Listing 5.21 Fetching the Errand and Its Actions
EXEC @aReturnValue = Errand_FetchById @id
--Leave error handling for chapter 9.
EXEC @aReturnValue = Action_FetchByErrand @id
--Leave error handling for chapter 9.
Aftereachstatementthatcouldleadtoanerror,therewillbeaclauseinvestigatingtheresult.If
thereisanerror,aGOTO willbeusedforjumpingtotheExitHandler similartotheoneshownin
Listing5.22.TheExitHandler isalsoenteredwhenthecompletestoredprocedureexecutes
normally.First,intheExitHandler,thetransactionwillbetakencareof(moreaboutthatinthe
nextchapter),andthen,iftherewasanerror,thiswillberaisedwithRAISERROR() in
JnskError_Raise().(Inthatstoredprocedure,therewillalsobeaJnskTrace_Error() being
done.)Finally,therewillbeaJnskTrace_End(),andthenthelocal@anError variablewillbe
RETURNedtothecaller.
Listing 5.22 ExitHandler for Stored Procedure
ExitHandler:
IF @theTranCountAtEntry = 0 AND @@TRANCOUNT > 0 BEGIN
IF @anError = 0 BEGIN
COMMIT TRAN
END
ELSE BEGIN
ROLLBACK TRAN
END
END
IF @anError <> 0 BEGIN
EXEC JnskError_Raise @theSource
,@anError, @anErrorMessage
END
EXEC JnskTrace_End @theSource
RETURN @anError
Note
WewilldiscussfindingandtakingcareoferrorsindepthinChapter9,"ErrorHandling
andConcurrencyControl.
Evaluation of Proposals
Asinpreviouschapters,it'stimeformetoevaluatetheproposalsIhavepresentedinthis
chapter-thearchitectureandstandardizedcodeproposals.
Evaluation of My Architecture Proposal
ThearchitectureproposalthatIdiscussedindepthearlierinthischapterisquitedifficultto
evaluatefromthecriteriasetupinChapter2 becausethearchitectureiscreatedtobeasfriendly
aspossibleregardingjustthosecriteria.Still,let'stakealookathowitdoes.
ThearchitecturetriestobeconsumerindependentfortheBusinessandDatatiers.Thefirsttieris
consumerdependent(ofcourse)becauseitistheConsumertier,butattentionhasbeenpaidto
adaptingdifferentconsumersasmuchaspossibletotheBusinesstier,withthehelpofconsumer
helpers.
Thearchitectureoptimizescallsbetweentiersbecausethiswilloftenmeanprocess-to-processand
machine-to-machinecommunication.Performanceandscalabilityarealsoafocus.Still,thenumber
oftiersandlayersaddsoverheadeventhoughthenumberofcontextswillbereduceddueto
configurationsettingsorthankstotheuseofShared methods.
Oneofthemaingoalsofthearchitectureistoprovideahighdegreeofmaintainability.The
applicationsthatIhavebuiltwithsimilararchitecturesaremostoftenasheerjoyinwhichtomake
changes.Productivitymaysufferbecauseofthecomplexity,butitmayalsobenefitfromitthanks
totheopportunitiesforparallelworkfordeveloperteamsthataremade.
Thebusinessruleswillbeputinseverallayersinsteadofcentralizedtoasingl eone.Thatmeans
thatit'shardertomaintainthemthaniftheyarecentralizedtoonelocation.(We'lldiscussthis
moreinChapter7.)
Notmuchhasbeensaidaboutsecurityinthischapter.Thearchitectureisexpectedtooperate
insidetheintranet,sothereshouldbeasmallerriskofattacks.Thesensitivepartsofthe
applicationwilltypicallybeintheconsumer;evenso,therearepotentialsecurityrisksinthe
presentedarchitecture.Forexample,auserIDwilloftenbesentaroundbetweenlayersasan
ordinaryparameter,butitcouldbepossibletocatchandchangethat.Onceagain,don'tforgetthat
theapplicationwillmostoftenbeinsideofthefirewalls.
Evaluation of Standardized Code Structures Proposal
Earlierinthischapter,Igaveyouseveralproposalsforcodestructuresfromthedifferentlayers.
Performanceandscalabilityarereducedbecauseofthetracecallsandsuch.
Whenversion1of.NEThasbeenreleased,youwillfindsometestresultsatthebook'sWebsiteat
www.samspublishing.com.Inthesetests,Ishowtheoverheadofthecodestructurespresentedin
thischapter.
Productivitygainsfromtheusageofmetadata.Productivityalsogainsfromhavingcodestructures
tostartfromandfromallthegeneralmethods/storedprocedurestocalltogettasksdone.The
sameappliesformaintainability,whichalsobenefitsfromthecommonstructure,asdo
debuggabilityandreusability.Andfinally,reliabilitybenefitsfromthefocusonerrortrapping.
What's Next
Animportantaspectofarchitectures,andsomethingthathasmanyimplicationsformyproposed
architecture,istransactions.Inthenextchapter,Iwilldiscusssuchtransactions-notmuchabout
whattheyareandhowtheywork(asisdealtwithinpracticallyallbooks),buthowtoreasonwhen
choosingacertainstylefortakingcareoftransactions,andtherewillbeseveraltipstousealong
theway.
References
1.P.Heinckiens.Building Scalable Database Applications.Addison-Wesley;1998.
2.M.StonebrakerandP.Brown.Object-Relational DBMSs: Tracking the Next Great Wave, Second
Edition.MorganKaufmannPublishers;1999.
3.Introduction to the Duwamish Online Sample Application;
http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/dnduwon/html/d5dplywindna.asp.
4.Duwamish Online SQL Server XML Catalog Browsing;
http://msdn.microsoft.com/library/default.asp?url=/library/en-
us/dnduwon/html/d51ctlgbrowse.asp.
5.F.Buschmann,R.Meunier,H.Rohnert,P.Sommerlad,andM.Stahl.Pattern-Oriented Software
Architecture: A System of Patterns.Wiley;1996.
6.StenSundbladandPerSundblad.Designing for Scalability with Microsoft Windows DNA.
MicrosoftPress;2000.
7.Fitch&MatherStocks2000:IntroductionandArticleList;
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfmstock/html/fm2kintro.asp.
8.E.Gamma,R.Helm,R.Johnson,andJ.Vlissides.Design Patterns: Elements of Reusable Object-
Oriented Software.Addison-Wesley; 1995.
9.T.Ewald.Transactional COM+: Building Scalable Applications.Addison-Wesley;2001.
10.C.Szyperski.Component Software: Beyond Object-Oriented Programming.Boston:Addison-
Wesley;1997.
11.P.HerzumandO.Sims.Business Component Factory.Wiley;2000.
Chapter 6. Transactions
IN THIS CHAPTER
N ChoosingaTransactionTechnique
N TransactionsintheProposedArchitecture
N AFlexibleTransactionDesign
N NewPossibilitiestoConsiderwith.NET
N TipsonMakingTransactionsasShortasPossible
N TipsonDecreasingtheRiskofDeadlocks
N ObscureDeclarativeTransactionDesignTraps
N EvaluationofProposals
TransactionaldesigniscrucialforasuccessfulOnlineTransactionsProcessing(OLTP)application,
andyetitisoftentotally"forgottenincomponent-basedapplications.Inthischapter,Idiscuss
severaldifferenttransactiontechniquesandrecommendwaystochoosebetweenthem.Ithen
discusstransactionsinthecontextof theproposedarchitectureofthepreviouschapterandshow
howyoucandesignyourapplication,makingiteasytochangetransactiontechniquesifneedbe.
Next,welookatthechangesbroughtaboutby.NETconcerningtransactionsanddiscusstipson
gettingshortertransactions,lesseningtheriskofdeadlocks,andavoidingtrapswithautomatic
transactions.Finally,Ianalyzemyvarioustransactionproposalsbasedonthecriteriaestablished
inChapter2,"FactorstoConsiderinChoosingaSolutiontoaProblem.
Note
Thischapterdiscussesgeneralsolutionsfortransactions.InChapter8,"DataAccess,I'll
bindthesolutionstomydataaccessproposalandspecificallytothearchitecture.
Lockingandconcurrencycontrolareattheheartoftransactions.Wewilltouchonthe
subjectinthischapter,butwe'llalsodiscusslockingandconcurrencycontrolinmore
detailinChapter9,"ErrorHandlingandConcurrencyControl.
Beforewegetstarted,it'simportantthatyouhaveafirmgraspofthefollowingtopicsregarding
generaltransactiontheoryandautomatictransactionsin.NETbecauseIwillnotdiscussthemin
detailinthischapteroranywhereinthisbook.
N Atomicity,Consistency,Isolation,andDurability(ACID)
N Sharedandexclusivelocks
N TransactionIsolationLevels(TIL)
N Two-PhaseCommit(2PC)
N Doomed,done,andhappyflags
N Whatthedifferenttransactionattributevaluesstandfor
Note
Ifyoufeelyouneedtocatchuponthesetopics,IrecommendTimEwald'sTransactional
COM+: Building Scalable Applications
1
orTedPattison'sProgramming Distributed
Applications with COM+ and Visual Basic 6.0.
2
Todelveevendeeperingeneral
transactiontheory,IrecommendJimGrayandAndreasReuter'sTransaction Processing:
Concepts and Techniques
3
orPhilipA.BernsteinandEricNewcomer'sPrinciples of
Transaction Processing.
Choosing a Transaction Technique
Asyouknow,youcanchoosefromseveraldifferenttechniqueswhendealingwithtransactions.In
thissection,Icomparedistributedtransactionswithlocaltransactionsanddiscussthedifferent
approachestoworkingwiththosetypesoftransactions,suchasusingtransactionalserviced
components,ADO.NET,andstoredprocedures.First,let'sstartbylookingatthemaingoalofany
chosentransactiontechnique.
The Main Goal of Any Chosen Transaction Technique
AsImentionedinChapter2,themaingoalofanychosentransactiontechniqueisthatitproduces
correct resultswhenneededforcertainscenarios.Keepthisinmindwhenyoureadthischapter's
discussionsofperformance,scalability,maintainability,andsoon.Correctnessismostimportant.
Althoughyoumaythinkthatitisagiventhatcorrectnessisextremelyimportant,thisnotiongoes
onestepfurtherwithtransactions.RecallwhatIsaidinChapter1,"Introduction,aboutthenew
featureofCOM+1.5calledprocessrecycling.Althoughnobodylikesamemoryleakandwealltry
toavoidand/ortrytofindthem,they'reoftennotalargeproblem,evenforcriticalWebsites-you
justrecycletheprocessonceadayandnobodynotices.However,if,attheverysameWebsites,
onetransactionadayoramonthproducesanincorrectresultleadingtoaninconsistentdatabase,
suchleaksbecomedisasters.ThegoodnewsisthatallthetechniquesIdiscussinthischaptercan
beusedtocreatecorrecttransactions.Havingsaidthat,let'sfocusonissuesofrawperformance.
Description of the Selection of Transaction Techniques
It'spossibletocategorizetransactiontechniquesinseveralways.First,wecancategorizethemas
beinglocal(asareordinaryT-SQLtransactions),beingtakencareofbyoneSQLServerinstance,
orasbeingdistributedas2PCtransactionscoordinatedbyMicrosoftDistributedTransaction
Coordinator(DTC).Transactiontechniquescanalsobedescribedasbeingautomaticormanual.In
automatictransactions,thedesiredtransactionsemanticsaredeclaredratherthanprogrammed.In
manual transactions,thetransactionsarecontrolledwithexplicitstartandendstatements.
Note
Don'tconfuseautomaticandmanualtransactiontechniqueswiththeimplicitandexplicit
transactionsin,forexample,T-SQL.Asyouprobablyknow,whenyoudoanUPDATE in
SQLServerwithoutfirststartingatransaction,theUPDATE willbewrappedinsidean
implicittransaction.Ifyoubeginandendyourtransactiononyourown,youcreatean
explicittransaction.
Thefinalcategoryiscontrollertechnology,suchasADO.NET(thatwrapslocalT-SQLtransactions)
andthetwowrappersforDTCtransaction(namely,COM+transactionsandT-SQLdistributed
transactions).Thelastandverycommontransactionwrapperisn'treallyawrapper.I'mreferring
tomakingpureT-SQL transactions.Ifweusethewrappersascategories,thesituationshownin
Table6.1occurs.
Table 6.1. Transaction Wrapper Techniques
Wrapper Manual/Automatic (Programmed/Declared) Local/Distributed
COM+transactions Automatic Distributed
ADO.NETtransactions Manual Local
DistributedT-SQLtransactions Manual Distributed
PureT-SQLtransactions Manual Local
Note
InTable6.1,youseethatusingCOM+transactionsalsomeansusingdistributed
transactions.Thisisoftenoverkill,especiallyifyouhaveonlyoneResourceManager(RM)
participatinginthetransaction.Eventhoughadelegatedcommitwillbeusedtooptimize
awaysomeoverheadfromthe2PCprotocol,thiskindoftransactionisexpensive.
Ihaven'tuseddistributedT-SQLtransactionsinanyreal-worldapplications,andIoftenfindthisto
bealesscommonlyusefultechnique.Therefore,Iwillnotdiscussitinanydetailnowwhen
describingwrappers.Instead,I'llbrieflydescribethedifferentwrapperssothatyouunderstand
whatImeanwhenIusethedifferentnames.NotethatIexpectyoutohaveafirmgraspofthe
techniquesIpresentnextsoIwillonlydescribethembriefly.
COM+ Transactions
WhenCOM+controlledtransactionsareused,yougetautomaticanddistributedtransactions.
COM+asksMicrosoftDistributedTransactionCoordinator(DTC)forhelpwiththephysical
transaction,butCOM+willtellDTCwhentostartandwhentoendthetransactionwiththeDTC-
enabledRM(s).Thetransactionalbehaviorisdeclaredonthecomponentswithtransaction
attributes,andthenCOM+usesinterceptiontostartandendtransactions.Ifyouusethe
AutoComplete() directiveonthemethods,youdon'thavetowriteanycodetomanagethe
transactions.YoucanseethisinListing6.1,whereastoredprocedureiscalledandCOM+is
startingandendingatransaction.Otherwise,youshoulduse,forexample,
ContextUtil.SetComplete() andContextUtil.SetAbort() tovotefortheoutcome.
Note
Althoughyouobviouslydonothavetousestoredprocedures,Ihighlyrecommendit.In
Chapter5,"Architecture,Irecommendedthatyoualwaysusestoredprocedureswhen
componentscallthedatatier.Thispresentsanumberofproblems,buttheyaresolvable.
InChapter8,"DataAccess,Ipresentafewexamplesofsuchproblemsandoffer
suggestionsfortheirsolution.
Listing 6.1 An Example of a COM+ Controlled Transaction
aCommand.ExecuteNonQuery()
Note
It'snotonlydatabaseenginesthatareRMs.Don'tforgetthatQueuedComponents(QC)
andMSMQareotherexamplesofRMs.In thefuture,IhopetoseeDTC-enabledRMsfor
Exchange'sdatastorage,theWindowsfilesystem,andsoon.
Amajoradvantagewithautomatictransactionsisthatyoucanoftenreusecomponentswithout
codechanges,andtheycandirectlyparticipateinthetransactionsofthenewconsumers.One
reasonforthisisthatallthecomponentsparticipatinginonetransactioncanopenaconnectionof
theirown.Theconnectionswillauto-enlistinthetransaction.
ADO.NET Transactions
ADO.NETtransactionsareconceptuallyreallyjustawrapperaroundordinaryT-SQLtransactions.
Listing6.2showsanexampleofhowADO.NETtransactionscanbeused.Inthiscase,thereisa
transactionaroundacalltoastoredprocedure.Ofcourse,thereismoretoitthanthat-youhave
tomakeanaConnection.RollbackTrans() atthetimeofanexception.
Listing 6.2 An Example of an ADO.NET Controlled Transaction
aTransaction = aConnection.BeginTransaction()
aCommand.Transaction = aTransaction
aCommand.ExecuteNonQuery()
aTransaction.Commit()
Pure T-SQL Transactions
AlthoughT-SQLtransactionsareusedbyADO.NETtransactions,whenIreferto"pureT-SQL
transactions,I'mreferringtoT-SQLtransactionscontrolledbySQLscriptsorbystoredprocedures.
Listing6.3showsanexampleofhowthismightlook.Onceagain,Ihaveexcludedthecodefor
ROLLBACK TRAN andthecompletecodestructurediscussedinChapter5.
Listing 6.3 Simplified Example of a Pure T-SQL Transaction
BEGIN TRANSACTION
UPDATE errand
SET closedby = @userId
, closeddatetime = GETDATE()
, solution = @solution
WHERE id = @id
INSERT INTO action
(id, errand_id
, description, createdby
, createddatetime, category)
VALUES
(@anActionId, @id
, @description, @userId
, GETDATE(), @aCategory)
COMMIT TRANSACTION
Why Care About Which Transaction Technique to Use?
Therearehugedifferencesbetweenthedifferenttechniqueswhenitcomestoperformanceand
scalability.Thebiggestdifferenceisbetweenlocaltransactionsanddistributedtransactions,
becausethe2PCprotocolusedbydistributedtransactionsisprettyexpensiveasfarasoverheadis
concerned.Table6.2presentsasubjectiveoverviewoftheadvantagesanddisadvantagesofeach
techniquewhenyouworkwithoneRM.Notethatthelowerthevalue,thebetter.
Table 6.2. Comparison of the Transaction Techniques for One RM
Factor
COM+
1ransactions
ADO
1ransactions
Pure 1-SQL
1ransactions
Throughput 3 2 1
Participationintransactionstogetherwithother
(unknown)components
1 3 3
Gettingportablecodewithregardtodifferent
databaseproducts
1 1 3
Fine-grainedcontrolofwhentostartandend
transactions
3 2 1
ADO and Symmetric Multi-Processing
Machines
Afewyearsago,IranatestofCOM+transactions,ADOtransactions(not
ADO.NET),andpureT-SQLtransactionswithVB6writtencomponents.I
waspuzzledwhenIsawthattheCOM+controlledtransactionshada
higherthroughputthantheADO controlledtransactions.Ittookmea
whiletounderstandthatitwasonlytrueonSymmetricalMulti-Processing
(SMP)machines.WhenIturnedoffoneofthetwoCPUsinmytest
applicationserver,thethroughputfortheADOcontrolledtransactions
actuallyincreasedandtheresultingrelationbetweenADOtransactions
andCOM+transactionsturnedoutasexpected.Youwillfindtheresults
fromatestofthesametechniquesbutinthisnewenvironmentof.NET
andADO.NETonthebook'sWebsiteatwww.samspublishing.com.
Reasons to Use Distributed Transactions
Youmaywonderwhy,giventhattheyaresoexpensive,Iamdiscussingdistributedtransactionsat
all.Thereasonsaresimple:
N There is more than one RM- IfyouhavemorethanoneRMthatmustparticipateinthe
transactions,youshouldusedistributedtransactions.
N Components that are unaware of each other coexist- Ifyouneedtoreuseacomponent
thatisoutofyourcontrolorifyoudon'twanttorewritethecomponenttofitintoyour
architectureoflocaltransactions,youcaneasilysolvetheproblemwithdistributed
transactionsinstead.Thisisalsoausefultechniqueforusinglegacycomponentsinthe.NET
world.
Conclusion and Proposal: Choosing a Transaction Technique
Myproposalforchoosingatransactiontechniqueissimple-usepureT-SQLtransactionsifyou
onlyhaveoneRM;otherwise,useCOM+transactions.Also,useCOM+transactionsifyouneed
transactionstospanunknowncomponentsthatarenotallwithinyourcontrol.Thisisanother
situationwhereCOM+transactionsshine.
Transactions in the Proposed Architecture
TransactiondesignisextremelyimportantandwasakeyfactorIevaluatedwhencreatingthe
architectureIdescribedinthelastchapter.Inthissection,I'lldiscusshowthetransactionsfitin
thearchitecture.Butbeforewedothis,let'sreviewtheproposedarchitecture.
Review of the Proposed Architecture
Figure6.1presentsthearchitectureforthesampleapplicationAcmeHelpDesk,whichyoufirstsaw
inChapter5.KeepthisfigureinmindinthefollowingdiscussionofwhatIconsidertobeimportant
informationaboutanearlierattemptofanarchitectureImade.Thefigureappliestobothmyold
andnewattempt.
Figure 6.1. My architecture proposal applied to the sample application Acme HelpDesk.
Earlier Architecture Attempt
I'veusedseveraldifferentarchitectureproposalsovertheyears.Onethat
Iusedalot-let'scallitthe"oldarchitecture-looksexactlyasshownin
Figure6.1,buttherearealotofdifferencestothecurrentproposal.This
oldarchitectureisn'tthelastoneIusedbeforemovingto.NET,butrather
somethingthatIusedafewyearsago.I'dliketodiscussitheretoshow
howmycurrentarchitectureevolved.
Whenusingautomatictransactionsintheoldarchitecture,Ionlyletthe
PersistentAccesslayerbetransactional,soalltheclassesinthe
ApplicationlayerandtheDomainlayerhadNotSupported forthe
Transaction attribute.(Theycouldalsohavehad Supported or
Disabled,dependingonwhatbehaviorwasdesired.)Theideawastoget
transactionsasshortaspossibleandnottoletatransactionspanmore
thanonelayer.
ThemaindrawbackwasthatusingtransactionsonlyinthePersistent
Accesslayerhadahugeinfluenceondesign.Alltransactionshadtoreach
onemethodinthePersistentAccesslayerinonecall.Ofcourse,thisis
possible,butitgaveunintuitivecodefortheApplicationandDomain
layersanditalsomadereusingtheDomainandPersistentAccessclasses
fordifferentApplicationclassesharder.
AnotherdrawbackwhenIusedthearchitecturewithmanualtransactions
wasthatIreliedonthestoredprocedurestocontrolthetransactions.The
problemwaswhenseveralstoredprocedureshadtobecalledfroma
PersistentAccesslayerclasstoparticipateinonesingletransaction.In
thissituation,IletADOcontrolthetransaction.
YetanothernegativeaspectaboutthisproposalwasthatIsplitallthe
classesinthePersistentAccesslayerintotwocategories-oneforfetching
withtheTransaction attributesettoSupported andoneforwritingwith
theTransactionattributesettoRequired.Yetanotherdrawbackwasthat
becauseIletthePersistentAccesslayerbetransactional,Ihad
interceptionandnondefaultcontextsforboththeclassesinthe
ApplicationlayerandthePersistentAccesslayer.
Transactions in the Current Proposal
WhenIuseautomatictransactionswiththecurrentproposal,Ionlydeclaretransactionalbehavior
ontheclassesintheApplicationlayerbysettingtheTransaction attributeofthetransactional
classestoRequired.(TheclassesintheotherlayerswillhaveDisabled asthevalueoftheir
Transactionattributeiftheyaren'tShared.IftheyareShared,noTransactionattributeswillbe
used,norwillinheritancefromServicedComponent.)Usually,thefirstcontactwiththedatabasein
ascenariowillbedeferreduntiltheveryendwhentheSQLscriptistobeexecuted.Consequently,
thephysicaldatabasetransactionwillnotbelongerbylettingtheApplicationlayerbe
transactional.
Owingtothissolution,thedesignwillbelessinfluencedbythetransaction.Theusecaseclassin
theApplicationlayercanaskseveralDomainandPersistentAccessclassesforhelpbycalling
primitivemethods,insteadofhavingtomovethecontrolovertoaPersistentAccessclass.The
classresidingintheApplicationlayerwillcontrolthecompleteusecaseand,attheend,willsend
theSQLscripttoahelperclassinthe PersistentAccesslayerforexecution.
TakealookattheinteractiondiagraminFigure6.2foranexampleofacallsequence.Here,you
canseethattheErrandSaving classfromtheApplicationlayercontrolsthescenario.First,itcalls
thedoErrand classintheDomainlayertocheckwhetherthenewerrandisacceptableaccordingto
thedefinedrules.Then,itcallsthepaErrand classinthePersistentAccesslayertoreceivethe
requiredrowstotheSQLscript.Finally,theSQLscriptisexecutedwiththepaHelper classandthe
publicstoredprocedurea_Errand_Insert() iscalled.Finally,theprivatestoredprocedure
Errand_Insert() willbecalled,andtheINSERT statementwillbeexecutedthere.
Figure 6.2. Interaction diagram, showing an example of a transaction.
BecauseanSQLScriptisused,thereisnoneedtouseADO.NETcontrolledtransactions.Instead,
thetransactioncanbestartedandendedintheSQLScript.Thereisnorealreasonforsplittingthe
PersistentAccessclasses(paErrand inFigure6.2)intotwopartseither.
Themaindrawbackwiththecurrentproposalisthatwithautomatictransactions,amethodthat
doesafetchfromthedatabaseanddoesn'tneedatransactionwillgetaDTCtransactionifitis
locatedinatransactionalApplicationlayerclass.Whenmanualtransactionsareused,itisn'ta
problemtohaveasingleclassforbothfetchingandupdatingmethodsbecauseIcontrolexactly
whentostartandstoptransactions.Ifitisarealproblemwithautomatictransactionsthatfetching
methodsalsostartsDTCtransactions,theclassesintheApplicationlayerhavetobesplit.Thisis
unfortunatebecausetheusecasethenneedstwodifferentApplicationlayerclasses.Fortunately,
theConsumerHelperlayercanhidethefactthattheclasshasbeensplitfromtheConsumerlayer.
A Flexible Transaction Design
IfyoufollowtherecommendationtouselocaltransactionswhenyouonlyhaveoneRM,butwant
toprepareforapossiblefuturechangetodistributedtransactionsandhaveasfewprogramming
changesaspossible,thereareacoupleofthingstothinkabout.
Transaction Control
AssumeyouhavefollowedthearchitecturethatI haveproposed.Forsimplicity'ssake,weonly
haveonerootcomponentintheApplicationlayerthatiscalledErrandSolving inthisscenario.It
willuseacomponentcalledpaHelper inthePersistentAccesslayer.Inturn,thepaHelper willuse
astoredprocedurecalleda_Errand_Close().ThestoredprocedurewillUPDATE arowinthe
errand tableandINSERT arowtotheaction table.BecausethereisonlyoneRMinvolvedinthe
transaction,Iamusingalocaltransactioninthestoredprocedure.Listing6.4showshowthismay
look,inasimplifiedversion.
Note
IwilldiscusserrortrappingingreaterdepthinChapter9.
Listing 6.4 Excerpt from Stored Procedure Showing a Simplified Pure T-SQL Transaction
BEGIN TRANSACTION
UPDATE errand
SET closedby = @userId
, closeddatetime = GETDATE()
, solution = @solution
WHERE id = @id
INSERT INTO action
(id, errand_id, ...)
VALUES
(@actionId, @id, ...)
COMMIT TRANSACTION
Transaction Attribute Value
Howshouldyoudeclarethetransaction-relatedattributesforthecomponentsinthissituation
whenpureT-SQLtransactionsareused?Thereareseveralcorrectwaystodoit.Onepossible
proposalistousethesettingsshowninTable6.3.
Note
IassumeherethatpaHelper isn'tShared.AsyoureadinChapter5,itispreferableto
use Shared wheneverpossiblefromaperformanceperspective.
Table 6.3. Transaction Settings: Proposal 1
Component Transaction(TransactionOption)
ErrandSolving Supported
paHelper Supported
BecauseErrandSolving istherootcomponentforthisscenarioandusesSupported transactions,
therewillbenoDTCtransactionstarted.ThisisexactlytheresultIwant.ButdidIachievethe
resultinthecheapestway?No,Icandobetterthanthis.Aslightimprovementwouldbetochange
paHelper tohaveDisabled insteadofSupported.Thismakesitpossibletosaveonecontext,but
I'mstillnotsatisfied.Let'sinvestigatewhynot.
WhenSupported isusedastheTransaction(TransactionOption) value,itmeansthatyouhave
tousejust-in-timeactivation(JIT).AsyourecallfromthediscussionaboutJITinChapter5,I
prefertouseJITonlywhenIneedCOM+transactions,sothisisn'ttheperfectsolution.Another
waytoseeitisthatbecauseSupported requiresinterception,objectsofthiscomponentcan'tgoto
thedefaultcontext.
Beforeyousaythatyoudon'tlikethissolution,rememberthatwithoutanyredeclarations,
instancesofboththecomponentscanparticipateinaCOM+transaction.Ifyoureallyneedthe
componentstobeabletoparticipateinaCOM+transaction,thissolutionisn'tsobadafterall.(It's
notallthatittakes,butit'sthefirststep.)
AmoreefficientdeclarationwouldbeasshowninTable6.4,inwhichthecomponentscanstill
participateinanouterCOM+transaction.
Table 6.4. Transaction Settings: Proposal 2
Component Transaction(TransactionOption)
ErrandSolving Disabled
paHelper Disabled
Nowtherewillbenointerception(ifnoinstancesofthosecomponentswillco-locateinacontext
thatusesinterception).Thereisalsothepossibilityofco-locationthatIdiscussedinChapter5.
Lessmemorywillbeusedandtherewillbelessoverheadforcreationandcallingmethods.
Thegoodthingisthatifyouaddanewcomponentthatrequiredatransaction,say
CustomizedErrandSolving,acreatedinstanceofErrandSolving canparticipateinthe
transaction.ThisassumesthatinstancesofErrandSolving mustco-locateinthecontextof
instancesofCustomizedErrandSolving.Wethenhavethebestofbothworlds.
Thedrawbackisthatco-locationisoftenaproblemforinstancesintheApplicationlayerbecause
thatlayeroftenrequirescomponent-levelsecurity,andthenco-locationisimpossible.Thisisalso
thecaseforrootcomponents.IftheconsumerisWindowsFormsonanothermachine,thereisno
contexttoco-locateinfortheApplicationLayerclass.WeshouldstillusethesettingsfromTable
6.4,butinstancesofanewand,today,unknowncomponentcan'tstartatransactioninwhich
instancesofErrandSolving canparticipate.Inanycase,wehavedeclaredtransactionattributes
asbeingasefficientaspossibleforthecurrentsituation.Andwehaven'tcreatedanyobstaclesso
farforeasilychangingthetransactiontechniquetoCOM+transactions.
Changing the Values for Transaction Attributes
AtfirstIthoughtI'drecommendthesolutionthatI'mnowinthemiddleofdescribingasameans
foradministratorstochangetransactiontechniqueswhentheyneededto.WhenImentionedthis
ideatoJoeLongatMicrosoft,hestronglydisagreed,andwehadalongdiscussiononthematter.
Hismainconcernwasthatadministratorscan'tknowabouttheinnerworkingsofthecomponents
andtheassumptionstheprogrammersmadewhentheywrotethecode.Evenforwellandstrictly
architectedcomponentswithfulldocumentationofsupportedtransactionsettings,thereisarisk
thattheadministratormaymakeasmallmistake.Andweallknowwhatonemistakecandotothe
transactions.Thereasonforusingdeclarativetransactionsisn'ttomaketransactionsemantics
easierforadministratorstomaintain,easierfordeveloperstoprogram,ormoreefficient.The
reasoniscorrectness!
IfwecomparesomeoftheEnterpriseServices attributeswithotherattributesin.NET,thereisa
fundamentaldifference.SomeoftheEnterpriseServices attributescanbechangedfromoutside
ofthecode.Inthefuture,itwillprobablychange,sowecanlockthesettings,butforthetime
being,it'sveryeasyforsomeonetochangethesettingsintheComponentServicesExpl orer.
Anotherflawwithmyoriginalreasonformyideawasthattheadministratorhaslittleopportunity
tomakeuseoftheflexibilityIwasabouttopropose.Therearetwobasicsituationswhenthe
transactiontechniqueneedstobechanged:
N ThecurrentcomponentsalsoneedtohitanotherRM.
N Thecurrentcomponentsneedtobeusedbyothercomponents,whichinturnhitanother
RM,ortheyuseanotherarchitectureforcontrollingtransactions.
Inthefirstcase,itwillusuallybethedeveloperswhomakethatchangeanyway.Inthesecond
case,Ithinkit'sreasonabletocontactthedeveloperstoo.
Anotherreason-andcertainlyabigone-fornotlettingadministratorschangethesettingsinthe
ComponentServicesExplorerforcertainattributesisthatthecommonlanguageruntimewon't
lookintheCOM+catalog,butratherinthemetadatafortheassemblytoknowhowtodealwith
componentsregardingObjectPooling,forexample.Theonlyvaluesthatshouldbechangedinthe
ComponentServicesExploreraredeployment-relatedvalues,suchasobjectconstructionstrings,
numberofobjectsintheobjectpools,andsimilardeployment-relatedvalues-notthesettings,for
example,thatenableobjectconstructionstringsorobjectpooling.
So,Joewonthebattle.Iagreewithhimthatsettingthetransactionattributesisamatterforthe
developers.Becauseofthis,Ihaveslightlychangedthebasicpurposeoftheproposal,andI'mnow
discussingitasawayfordeveloperstopreparetheirdesignandcodeforafuturechangeof
transactiontechnique.Theywillcarryoutthechange,butitwillbeeasilydone.
Note
Itmightsoundstrangetotalkabouttheadministratorasapersonwhowouldbethinking
aboutusingoneofyourcomponentsfromanothertransactionalcomponent.However,it's
veryeasytopublishXMLWebserviceswhosemethodsaretransactionalandthatcall
yourcomponents.Meanwhile,BizTalkhasbeenusedtoorchestrateasolutioninwhich
someofyourcomponentswillparticipate.Thinkaboutthisbeforeyoufollowmy
recommendationofusingDisabled forthetransactionattribute.Aretheimplications
reasonableinyoursituation?
Starting with Manual Transactions
Programmingforafuturechangeoftransactiontechniquemuststartwithcodingformanual
transactionsandthenmovetoautomatictransactions,andnottheotherwayaround.Why?If
everythingworkswellformanualtransactions,thereisagoodchancethatitwillworkwellfor
automatictransactionstoo.
Ontheotherhand,ifyoustartcodingforautomatictransactions,it'sverycommontouseseveral
connectionsinthesametransactionsothatparticipatingcomponentsopentheirownconnections.
Thisworksthankstoauto-enlist,sothatalltheconnectionswillbeenlistedintheautomatic
transaction.Inaway,thisisinformationhiding(whichInormallyappreciate)becausethedifferent
componentsaremoreshieldedfromeachothersotheywon'tsendaroundaconnectionobject.On
theotherhand,thisislessefficientthanlettingallinstancesthatparticipateinthetransaction
shareoneconnection.Asyousee,Iprefertolettheinstancessharetheconnectionbecauseit's
efficientandworkswithbothautomaticandmanualtransactions.
It'simportanttonotethatyoushouldn'tcountonallRMsbeingabletosuccessfullycommita
transactionwhereseveralconnectionshavebeenusedinthesameDTCtransaction.Thisworks
wellwithSQLServer;however,atthetimeofwritingitdoesnotworksowellwith,forexample,
Ingres.(I'veheardthataversionofIngresthatistobereleasedsoon-orhasbeenreleasedwhen
youreadthis-willsupporttransactionsthatspanseveralconnections,butitisn'tdocumentedasa
requirementbyMicrosoftthat"TightlyCoupledXAThreadsareamustforanRMtosupportDTC
transactions.)AsaferecommendationistouseonlyoneconnectioninyourDTCtransactionstoo.
Ontheotherhand,becarefulthatyoudon'tsendaconnectionbetweenprocessesormachines.
Note
YouwillseeinChapter8 whereIproposehowtoaccessthedatabase,thatonlyone
methodwillhitthedatabaseforeachscenario.Theparticipatinginstanceswilljustadd
logictoanSQL scriptthatisexecutedagainstthedatabasefromasinglemethod
afterward.
Therearetwoproblemsinstartingwithmanualtransactionsandthenexpectingyourcomponents
toworkwithautomatictransactionswithoutcodechanges.Thefirstisthatyouhavetoremember
thatautomatictransactionsrequireJIT,sothatthememberstateislostwhenthetransaction
ends.WhatImeanisthatyouhavetocodeyourlocaltransactionsasifJITwerebeingused.That
isdefinitelymyintentionwiththearchitectureproposal.
Thesecondproblemisthatyougetslightlydifferenttransactionsemanticsinthetwocases.You
candelaytransactionstartwithlocaltransactions,andyouwillalso,bydefault,workwithanother
transactionisolationlevel(TIL).Sowatchout!
One Controlling Part
It'spossibletouse,say,BEGIN TRANSACTION andCOMMIT TRANSACTION inyourstoredprocedures,
eveniftheyaretobeusedfromCOM+components.SQLServerwillkeeptrackofthecurrent
@@TRANCOUNT todeterminewhetherCOMMIT TRANSACTION reallymeansaCOMMIT orwhetheritis
onlysubtractingonefrom@@TRANCOUNT.That'sperfectlyacceptable.
Nevertheless,thereisatleastoneproblemwiththis.ROLLBACK TRANSACTION won'tjustsubtract1
from@@TRANCOUNT;itwilldoaROLLBACK of thecompletetransaction,whichcancreateaproblem
forthecomponents.Inmyopinion,it'smuchbettertodecideonjustwhoisresponsiblefordoing
something,andthennobodyelsewillinterfere.(Atleastnotaslongasthefirstpartydoesthe
job.) Therefore,Iwon'tdoaBEGIN TRANSACTION andCOMMIT TRANSACTION/ROLLBACK
TRANSACTION inmystoredproceduresifCOM+transactionsareresponsiblefortakingcareofthe
transactions.However,thismakesarealmessifthestoredproceduresarealsotobeusedfrom
otherconsumersthatdon'thandletransactionsontheirown.
Movingfromautomatictomanualtransactionsforsomescenarioswillalsotakealotofwork.
However,IusethesolutiontotheproblemshowninListing6.5.@@TRANCOUNT isstoredinalocal
variablewhenthestoredprocedureisentered.Whenthestoredprocedureisabouttostarta
transaction,itinvestigateswhetherthereisalreadyanactivetransaction.Ifthereis,therewon't
beanotherBEGIN TRANSACTION.AtCOMMIT/ROLLBACK time,asimilartechniqueisused.Ifthere
wasn'tanactivetransactionwhenthestoredprocedurewasentered,itshouldbe
COMMITted/ROLLedBACK now.Youshouldalsoaddtothisthecriteriathattheremustbeanactive
transaction.(TherecannowbeseveralCOMMIT sectionsinthestoredprocedurewithoutcreating
anyproblems.)Cleanandsimple.
Listing 6.5 Excerpt from a Stored Procedure Showing How to Write Flexible Transaction
Code
SET @theTranCountAtEntry = @@TRANCOUNT
IF @theTranCountAtEntry = 0 BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
END
UPDATE...
--Another DML-statement...
INSERT...
ExitHandler:
IF @theTranCountAtEntry = 0 AND @@TRANCOUNT > 0 BEGIN
IF @anError = 0 BEGIN
COMMIT TRAN
END
ELSE BEGIN
ROLLBACK TRAN
END
END
Note
Oracle,DB2,andeventheSQL-99standarddonotsupportBEGIN TRANSACTION,butthe
firstSQLcommandwillstartthetransaction.
Somethingelseyoumayneedtoaddtothissolutionishandlingthetransactionsthatspanover
severalstoredproceduresfromyourADO.NETcode.Thenyoucanuse
ContextUtil.IsInTransaction() todeterminewhetheryoushouldstartanewADO.NET
transaction.(Youcouldalsoaskthedatabaseserverforthe@@TRANCOUNT valuefromyour
component,butthatwouldleadtoonemoreroundtrip,andyoudefinitelydon'twantthat.)
Note
InsteadofstartingtransactionsthatmustspanseveralstoredproceduresfromADO.NET,
IdealwiththisintheSQLscript.Thisisattheheartofthedataaccesspatternpresented
inChapter8.
Transaction Isolation Level (TIL)
IfyouuseCOM+transactions,theTransactionIsolationLevel(TIL)willbesetforyou.Inthecase
ofCOM+1.5,youcanconfiguretheTILyouwanttohave.ForCOM+1.0,itwillalwaysbe
SERIALIZABLE.It'sbettertobesafethansorry.
Note
YoucanchangetheTILwithinCOM+1.0transactionsbyusingSET TRANSACTION
ISOLATION LEVEL statementsandoptimizerhints.WithSQLServer,thishasadirect
effect,butwatchoutbecausethebehaviordiffersbetweendifferentdatabaseproducts.
Ifyouusemanualtransactions,youhavetosettheTILonyourown.(ThedefaultforMSSQLis
READ COMMITTED.)ItypicallydothiswhenIstartthetransactionseeninListing6.5.Theproblem
isthat,forinstance,apublicstoredproceduredoesn'tknowwhichTILisneededbyausedprivate
storedprocedure.Itmightthenbethecasethat thepublicstoredprocedureSETsREPEATABLE
READ,buttheprivatestoredprocedureneedsSERIALIZABLE.Inthiscase,theprivatestored
proceduremusthaveitssettingoutsideoftheIF clause,soitexecuteseveniftheprivatestored
procedurewon'tstartthetransaction.SeeListing6.6foranexample.WhenthecodeinListing6.6
executes,thereisalreadyanactivetransaction(@theTranCountAtEntry isnot0,sotheBEGIN
TRANSACTION won'texecute),buttheTILwillstillbeincreased.
Listing 6.6 Increasing the TIL Within a Transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
IF @theTranCountAtEntry = 0 BEGIN
BEGIN TRANSACTION
END
Asyouunderstand,it'svery dangeroustochangetheTILincodedownthecallstack,butaslong
asyouonlyincreaseit,it'susuallyacceptable.Howcanyouknowthatyouhaveonlyincreasedthe
TIL?YoucanuseDBCC USEROPTIONS andfindanentrycalledisolation level thattellsyouthe
currentTIL.Ifyoudon'tfindthatentry,theTILhasn'tbeenchangedandithasthedefaultvalue.
Ontheotherhand,toavoidusingthisformycode,settingtheTILonlytoSERIALIZABLE canbe
doneoutsidetheIF-clause,asshowninListing6.6.
Tosummarize,Iusethefollowingrulesformystoredprocedures:
N Ifatransactionisneeded,itwillbestarted,butonlyifthereisn'tacurrenttransaction.
N TILisnotSET ifatransactionisalreadystarted,exceptifSERIALIZABLE istheneededlevel.
Thatis,SERIALIZABLE willbeSET,evenifthereisacurrenttransaction.
IusethefollowingruleswhenIgeneratetheSQLscriptinthecomponents:
N IftheSQLscriptonlycallsonestoredprocedure,atransactionisnotstartedfromtheSQL
script.(Thestoredprocedurestartsatransactionifitisneeded.)
N IftheSQLscriptcallsmorethanonestoredprocedure,theSQLscriptstartsatransaction
(ifitisneeded).Unfortunately,theonlysolutionhereisthattheinvolvedstoredprocedures
mustbeinvestigatedtodecidewhetheratransactionisneededandshouldbestartedfrom
theSQLscriptandwhatTILisneeded.Thiscanbetroublesome,butthereisnoshortcut.
Asyouknow,Ilovetocentralizecode,soItriedtowritehelperstoredproceduresformyBEGIN
TRANSACTION blockandmyblockforendingtransactions.Thiswouldhavegivenmecleanerand
smallerstoredprocedures,andIcouldalsohavehiddenimplementationdetails,suchasthatonly
SERIALIZABLE shouldbeSET evenifatransactionisn'ttobestarted.Unfortunately,Ifailed
becauseSQLServermonitorsthatthe@@TRANCOUNT musthavethesamevaluewhenyouexita
storedprocedureaswhenyouenteredit.Otherwise,therewillbeanerror.
Note
Acolleagueofmineoncesaidthatusinga high(andcorrect)TILisimportantifyouwork
withbankapplications,butyoucancheatabitanddecreaseitforsimpleadministrative
applications.Thismaysoundlikeadangerousviewpoint,butdon'tforgetthecontext.In
someapplications,ahighTILmaybeveryexpensiveand,atthesametime,
unnecessary.Atypicalexampleisapplicationsthatgeneratestatisticalinformation.
IfyouareunsureaboutwhichTILtouseforacertainscenario,goforahigherone,
presumablySERIALIZABLE.Ofcourse,youcouldstandardizeonSERIALIZABLE forall
scenariosintheapplication,butIprefertousethelowestcorrectTILforeachscenario.
AutoComplete() Versus SetComplete()/SetAbort()
Asyouknow,withCOM+transactions,youmustvoteontheoutcome.ShoulditbeCOMMIT or
ROLLBACK?Eveninthiscase,youhavetopayextraattentiontosupporttheflexibilitypatternthatI
discussinthissection.YoucancheckContextUtil.IsInTransaction() beforeyoudoa
SetComplete() andSetAbort() todeterminewhetherthereisatransactionforwhichoutcome
youmaychoose.Ifnot,there'snoneedtovote,andrememberthatSetComplete() and
SetAbort() don'tonlyvote,theyalsosetthedonebittoTrue,whichwillleadtoadeactivationof
theobject.BecauseIonlyuseJITforclassesthatuseCOM+transactions,Idon'twantthe
deactivationtooccurinothersituations.
Anothersolutiontotheproblemofvotingforwhattransactionoutcomeyouwanttohaveistouse
theAutoComplete() attributeonthemethodsforthetransactionalclassesinstead.With
AutoComplete(),araisedexceptionisunderstoodasaSetAbort(),butyoudon'thavetowrite
anycodeforit.Atthesametime,amethodthatendswithoutaraisedexceptionisthoughtofas
beingSetComplete().
AsomewhatsubtlepositiveeffectofAutoComplete() isthatyoudon'thavetohaveaCatch block
justtogetaplaceforyourSetAbort(),asshowninListing6.7.Inthiscase,youcanjustskipthe
catchblockbecausenothingisreallyhappeningexcepttheSetAbort().
Listing 6.7 Catch Block Only Due to Calling SetAbort()
Catch e As Exception
ContextUtil.SetAbort()
Throw(e)
AproblemwithAutoComplete() isthatifyougetanexceptionbutwanttomakeacompensating
actioninamethodhigherupinthecallstackandstillCOMMIT thetransaction,itisimpossibleif
AutoComplete() hasalreadyvotedforthesecondaryobjectanddeactivatedit.Inthiscase,the
transactionisdoomed.(Ofcourse,inthissituation,SetAbort() inthesecondarymethodwould
giveexactlythesameresult.DisableCommit() shouldbeusedinstead.)Anyway,Iusuallyprefer
totaketheeasyway.Ifthereisaproblem,thetransactionshouldberolledback,and,becauseof
this,theproblemdoesn'texistwithAutoComplete().Furthermore,ifyoursecondaryobjectco-
locatesinthecontextoftherootobject,thereisnoprobleminthefirstplacebecausethedoomed
flagisn'tsetuntilthecontextisleft.Asamatteroffact,it'snotagoodideaatalltoletyour
secondaryco-locatedinstancesvote.It'sbettertoonlyletoneinstanceinthecontextbe
responsibleforthevoting,preferablytheroot.
New Possibilities to Consider with .NET
.NETbringsusafloodofnewpossibilities,althoughnotallofthemareevenclosetooptimal.In
thissection,IpointoutafewweaknessesthatthemarketingdepartmentinRedmonddoesn'ttalk
muchabout.
Transactions and XML Web Services
AmethodonanXMLWebservicecanuseanautomatictransaction(owingtoaparameterofthe
WebMethod attribute).Thattransactioncanspanseveral.NETobjects,butitcannotspanseveral
XMLWebservices.Thetransactionwon'tflow.Atfirstthismayseemlikeagreatlimitation,butit
makessensebecauseofthefollowingreasons:
N Roundtripsbetweencomputersarealwaysexpensive.WhenXMLWebservicesareused,it
isnotbecausetheyarethemostperformanceefficientwayofcommunicating,itisbecause
ofotherreasons.WhatImeanisthattransactionsspanningseveralcallstoXMLWeb
serviceswouldbelongerthanwithothercommunicationmechanisms,andyoudon'twant
tohavelongtransactions.
N Typicalprotocolsfordistributedtransactions,suchasOLEtransactions(asareusedby
DTC),areinherentlyconnectionoriented.XMLWebservicesarenot.
N Youdon'tknowwhattechniqueis"hidingbehindthatotherXMLWebservice.Isitonethat
understandsOLEtransactions,forexample?ThereisnothingintheXMLWebservices
standardregardingtransactionsupport.
N SeveralXMLWebservicespublisherswouldcertainlybeveryreluctanttolettheconsumers
decideonthelengthofthetransactions.
Note
Ofcourse,youshouldpayalotofattentiontoensuringallthenecessaryinformationis
giventotheXMLWebserviceinonemethodcallsothatitcantakecareofthecomplete
transactionthenormalway.Thisisa"mustforallrootcomponentsintheApplication
layer,eveniftheyaren'ttobepublishedasanXMLWebservice.
Forthemoment,thewaytoproceedtogettransactionsemanticsoverseveralXMLWebservicesis
touseacompensatingmechanisminstead.Itwon'tbepossibletofulfilltheACIDproperties,but
thisisthebestyoucando.Unfortunately,theCompensatingResourceManager(CRM)won'thelp
youinthissituation,eventhoughitsnamesuggeststhatitwill.Youhavetorollyourownsolution
instead.
The Compensating Resource Manager (CRM)
TheCompensatingResourceManager(CRM)isrelativelyunknown,even
thoughithasbeenaroundsinceCOM+1.0firstsawthelightofday.CRM
helpstowriteatransactionalresourcedispenserofaresourcethatisn't
DTC-transactional.CRMistobeconsideredasyetanotherRM.Typical
examplesofresourcesthattheCRMareusefulfordealingwitharethefile
systemandMicrosoftExchange.CRMwon'tperformmagicandcreatea
trulytransactionalresourcedispenserforyou,butwiththehelpof
compensatingactions,youcangoalongway.
5,6
Note
Thereismoreandmoreinterestinusingsagasfortransactions.Asaga isalogical
transactionaggregatedfromasequenceofphysicaltransactionsthatmustallsucceedor
beundone.OnereasonforthenewinterestinsagasistheinterestinXMLWebservices.
Itisnotwithinthescopeofthisbooktodiscusssagasfurther.Formoreinformation
aboutsagas,seeTransaction Processing: Concepts and Techniques
3
andPrinciples of
Transaction Processing.
4
Flow of Services Through Remoting
AsyourecallfromChapter5,componentserviceswon'tflowthroughXMLWebservicesnor
throughRemoting.Actually,thelackofflowofcomponentservicesthroughXMLWebservicesis
usuallynotaslargeaproblemasitmayseematfirst.Mostoften,forreasonsofefficiency,you
havealltheservicedcomponentsthataretotalktoeachotheratthesameapplicationserver(and
inthesameAppDomain).Thereisthenno needforflowofcomponentservicesoverRemoting.
Ontheotherhand,ifyoudoneedtoletcomponentservicesflowbetweenmachines,youcan
alwaysrelyongoodoldDCOMforthat.Yes,DCOMisanightmarethroughfirewalls,butwhywould
youhaveafirewallbetweenyourservicedcomponents?Ifyoudohaveafirewallbetweenyour
servicedcomponents,askyourselfagainifthisisthecorrectdesigntouse.
Tips on Making Transactions as Short as Possible
Thelengthoftransactionsaffectsscalability.Eachtransactionholdsontoresources,suchaslocks
inthedatabase.Ifyoucanshortenyourtransactions,youcanservicemoreusers(and
transactions)withthesamehardware.ThefollowingaretipsIthinkareimportantinmaking
transactionsasshortaspossible.
Avoiding Large Updates
Ifyouhavetoupdateseveralrowsinatransaction,thetransactionwilltakelongerthanifonlya
fewrowsaretobeupdated.Norocketsciencehere.Evenso,itisworththinkingaboutbecause,
forexample,thismayaffectyourbatchprocesses,whichoftenupdatethousandsofrowsineach
transaction.Itisincreasinglysothatyoudon'thaveanydowntimewhenyoucanrunbatch
processeslikethiswithoutinterferingwithothertransactions.Therefore,itmaybeimportantto
useastrategyotherthanusinghugetransactions.Forexample,youcoulduseacompensating
solutioninsteadsothatifyouhavetoupdate100,000rows,youcanupdatetheminchunksof,
say,1,000ineachtransaction.Ifonetransactionfails,youwillseethatthetotaloperationisn't
atomic.Asaresult,youwillhavetocompensateforthis.Forexample,tryagainwiththose
transactionsthatweren'tupdatedthelasttime,orundotheresultofthetransactionsthatwere
updatedbefore.Watchout-thesecanbedangerousdesignchangestomake,andyouhaveto
makecarefulevaluationsbeforemovingalong.
Note
Notethatbatchprocessesdon'tgowellwithCOM+transactions.Firstwehavethe
timeout,sayingthatthecompletetransactionmaynottakemorethanxseconds.
(Normallyinproduction,xissetto5orlower.)ThenwehavetheJITbehaviorthatleads
tolongerexecutiontime,withoutanyrealuseinthissituation.Distributedtransactions
arealsoadrawbackbecausetheywillbemoreresourceconsuming,whichisnotwanted
ifyoudon'tneeddistributedtransactions.
Anothertypicalsituationarisingfromtoolargeupdatesiswhenyouhaven'tusedahighenough
normalform.Itthenmightbethecasethatacertainvalueislocatedinthousandsofrowsand,
whenthevaluehastobeupdated,youhaveaverylargetransactiontodealwith.Thesolutionto
thisissimple-useahighenoughnormalformforyourdatabasedesign,usuallythethirdnormal
formorhigher.
Avoiding Slow Updates
Foralltasks,thereareanumberofdifferentapproachestouse.Forrelationaldatabases,the
correctwaytoproceedisusuallytouseasettechniqueinsteadofarowtechnique.Let'stakea
simpleexample.Ifyouaregoingtoincreasethepriceofallproductsforacertaincategoryina
storedprocedure,youcandothisbyopeningaCURSOR withaSELECT thatfetchesalltherowsto
UPDATE.ThenyouiteratetheCURSOR andUPDATE rowbyrow.Itworks,butitwillbemuchslower
thanasimpleUPDATE thatupdatesall therowsinonestatement.Thismightbeobvious,butyou
shouldthinkinthiswaymoreoftenandforlessobviousscenariostoo.Thinktwiceifthereisaloop
inyourT-SQLcode-checkthatitisn'tadesignbug.
Loops
Althoughyouwanttoavoidloops,youmaysometimesneedtousealoopafterall.Typicalreasons
forthisare
N Youneedtoupdatealargenumberofrowsandyoudon'twanttofillthetransactionlog,or
youdon'twanttolockoutallotherusersfromthetable.
N Youneedtouseaverytrickyalgorithmthatcan'tbesolvedorthatcan'tbesolved
efficientlywithsettechniques.(However,don'tgiveuptoofast.)
N Youneedtocallastoredprocedureforeachrowinaresultset.
Eventhoughyouneedaloop,youdon'thavetouseaCURSOR.Asamatteroffact,Irecommend
thatyouuseaWHILE loopinstead.It'sasfastasorfasterthanaCURSOR inalmostallsituations.(If
theusedkeyisacompositeofmorethantwoparts,aCURSOR isslightlyfaster.)Thecodeisalso
simplerandcleanerwith aWHILE loop,andthereislesschanceofmakingmistakes,butonce
again,ifthekeyisacomposite,theWHILE codeismorecomplicatedthantheCURSOR code.
Let'slookatanexampleofwhataWHILE looplookslikeinaction.Iwillsolvethesameexample
thatIjustused(increasingthepriceofallproducts),butthistimeIwillupdatealltheproducts
withaWHILE loop,andIwillupdatethemcategorybycategory.Theproductsaresplitintoten
differentcategories.
InListing6.8,youcanseethatI firstSELECT whatisthesmallestcategoryfromtheproduct table.
IfIfoundacategory,theWHILE loopisstarted.IUPDATE alltheproductsfortheparticular
category,andIinvestigatethattheUPDATE wentallrightasusual.
Listing 6.8 WHILE Example: Part 1
SELECT @aCategory = MIN(category)
FROM product
WHILE @aCategory IS NOT NULL BEGIN
UPDATE product
SET price = price + @add
WHERE category = @aCategory
SELECT @anError = @@ERROR, @aRowcount = @@ROWCOUNT
IF @anError <> 0 OR @aRowcount = 0 BEGIN
SET @anErrorMessage = 'Problem with...'
IF @anError = 0 BEGIN
SET @anError = 81001 --An example...
END
GOTO ExitHandler
END
InListing6.9,youcanseethatIsavethelastprocessedcategoryinanoldvariable,andthenI
searchforthesmallestcategorylargerthanthelastprocessed.
Listing 6.9 WHILE Example: Part 2
SET @aCategoryOld = @aCategory
SELECT @aCategory = MIN(category)
FROM product
WHERE category > @aCategoryOld
END
Note
SeveralyearsagowhenIwasportinganapplicationthatIbuiltforSQLServer4.21to
SQLServer6,IdecidedtorewriteaWHILE looptoaCURSOR solution.IguessIwas
trickedbyallthehypeaboutserver-sideCURSORsthatwastakingplaceatthetime.It
wasprobablynotagoodidea, especiallybecausetheWHILE loopworkedjustfineand
thereweren'tanyproblemswithit.(Well,thereweren'tanyproblemswiththeCURSOR
either,butitwasprobablyaworsesolution.)Sincethen,I'velearnednottomake
transitionslikethesewithout havingarealpurposeandwithoutexaminingwhethersuch
transitionswillhaveapositiveeffect.
Avoiding Pure Object-Oriented Design
AsyourecallfromChapter5,Istressinthisbookthatobjectorientationisgreat,butithastobe
usedwisely.I'veseenpureobject-orienteddesignusedoftenforCOM+applications,andtheresult
hastypicallybeenscalabilitythat'stoolow.(I'venotonlyseenitused,I'vebeencontactedbye-
mailandinpersonseveraltimesandaskedwhattodointhesesituations.Myanswerhasalways
been"Redesign.)
Note
WhenIsaypureobject-orienteddesign,Imeanclassicobjectorientation,forexample,
withmanyproperties.Eachrowandcolumnfromthedatabasehasleadtoinstantiated
objects,andmethodshavebeenprimitivesothattoaccomplishatask,severalmethod
callsmustbemade.
Oneofthereasonsthatpureobject-orienteddesigndoesn't scaleisthatthetransactionswillstart
tooearly,andbecauseseveralobjectswillparticipateinthetransactionandeachoneofthemwill
talktothedatabase,thereisalsoalotofoverheadforroundtrips.Usually,storedproceduresare
notusedinthoseapplications,andiftheyareused,it'sonlyforprimitiveoperationssuchasone
SELECT,oneUPDATE,oneINSERT,oroneDELETE.
Itmightseemcompellingtohaveallthecodeinthecomponentsandnotletthedatabaseand
transactionsaffectthedesignatall.Unfortunately,itdoesn'tworkwellinlarge-scalesituations.
I'veheardwarstoriesaboutapplicationsbuiltthiswaynotscalingbeyondfivetotenusers.
Avoiding Pessimistic Concurrency Control Schemas
Anothertypicalreasonforhavinglongtransactionsistheneedtousepessimisticconcurrency
controlschemas.Trytoavoidpessimisticconcurrencycontrolschemasandyouwillgetshorter
transactions.Ofcourse,thedisadvantageisthatyoudon'tknowwhetheryourtransactionwillbe
abletorunwhenyouuseanoptimisticschemainstead,butmostoftenthisisthewaytogo.
WithWeb-basedapplications,it'snotusualtokeepaconnectionforauserbetweenpage
renderings.It'sthesameforallCOM+applicationswheretheconnectionisclosedandtheresultis
disconnectedfromthedatabaseandsentbacktotheuser.Becauseofthis,abuilt-inpessimistic
concurrencycontrolschemacan'tbeused.Inanycase,youneverwantausertodecidethelength
ofatransactionbyaskingtheuserforananswerbetweenstartofthetransactionandCOMMIT.
Note
InChapter9,Iwillshowyouanalternativetothebuilt-inpessimisticconcurrencycontrol
schemathatworksindisconnectedscenariosanddoesn'tcreatelongtransactions.
Using an Efficient Pattern for Data Access
Iwon'tgointodetailaboutmypatternfordataaccessuntilChapter8,butitiscrucialthatyou
haveaneffectivepatternfordataaccess.Asyoucanguess,Ithinkmypatternisaveryefficient
one.Ideferallaccesstothedatabaseuntiltheendofascenario.Untilthen,anSQLscriptisbuilt
withallthecallstodifferentstoredprocedures.Atransactionwon'tbestarteduntilit'sneededin
theSQLscript.WhentheSQLscripthasstartedtorun,therearenootherroundtripsandno
operationsotherthanthecallstostoredproceduresuntilthetransactionistobeended.
Starting Transactions Late and Ending Them Early
Supposethatyouhavefivetaskstoaccomplish.Quiteoften,onlytwooftheseneedtobedone
insidethetransaction,soyoushouldprograminthisway,ofcourse.Don'tdoanythinginthe
transactionthatyoudon'thavetodo.PreparethetransactionbeforeitstartsbygettingNEWID()s,
ifyouneedtoINSERT arowthathasaUNIQUE IDENTIFIER asthekey,forexample.Listing6.10
showsanexampleofasimplifiedstoredprocedureforreportinganewerrandand,atthesame
time,writinganactionbecausethereporteralsomadeafirstattempttosolvetheproblemthat
failed.
Listing 6.10 A Simplified Version of a Stored Procedure for Inserting an Errand and a
First Action
CREATE PROCEDURE a_Errand_Insert
(@userId uddtUserId, @errandDescription uddtDescription
, @actionDescription uddtDescription)
AS
DECLARE @anErrandId UNIQUEIDENTIFIER
, @anActionId UNIQUEIDENTIFIER
, @aCategory UNIQUEIDENTIFIER
SET @anErrandId = NEWID()
SET @anActionId = NEWID()
SELECT @aCategory
FROM category
WHERE description = 'report'
BEGIN TRANSACTION
INSERT INTO errand
(id, description, createdby, ...)
VALUES
(@anErrandId, @errandDescription, @userId, ...)
INSERT INTO action
(id, errand_id
, description, createdby
, createddatetime, category)
VALUES
(@anActionId, @anErrandId
, @actionDescription, @userId
, GETDATE(), @aCategory)
COMMIT TRANSACTION
Note
IhavecutouttheerrortrappingcodefromListing6.10.Iwillfocusonerrortrappingin
Chapter9.
AsyouseeinListing6.10,IwaiteduntilIhadcreatedthetwoGUIDsandIreadthecategory
GUIDbeforeIstartedthetransaction.Thedifferenceisn'thuge,butit'stheprincipleIliketopush.
However,whathappensifthisstoredprocedureiscalledtogetherwithotherstoredprocedures?
Thereisagreatchancethatyouwillneedtocreateanoutertransaction,andthenmy
recommendationhereisofnouse.Evenifitwon'tmatterinsomecases,itwillinothers.Thisis
alsoanindicationthatitmightbebettertoletthecomponentsprepareasmuchaspossiblebefore
startingthedatabasetransaction.IntheexampleinListing6.10,thiswouldmeanthattheGUIDs
willbegivenasparametersinstead.Itmightresultinalittlemorenetworktrafficbecausemore
datawillbesentoverthenetwork,butit'susuallyworthit.
Thatwastheusualrecommendation.Justbecarefulthatyoudon'toveruseitsothatyoufetch
rowsfromthedatabasethatwillnotkeeptheirsharedlockduringthecompletetransaction
becauseyouhadthefetch beforethetransactionstarted.
Using Correct TIL
BecarefulthatyouusethecorrectTIL.OverusingSERIALIZABLE candefinitelyaffectscalability.I
can'ttellyouhowmanytimesI'vehearddevelopersatCOM+newsgroupsaskwhythereisso
muchblocking inthedatabasewhentheystartusingCOM+.Theyaren'tdoinganythingunusual.If
youonlyexecuteaSELECT COUNT(*) FROM mytable,othertransactionswillnotbeallowedto
INSERT rowsINTO mytable untilthefirsttransactionisdone.COM+1.0alwaysuses
SERIALIZABLE forautomatictransactionswithSQLServer.YoucanconfiguretheTILforCOM+
transactionsinCOM+1.5.
WhenyouusepureT-SQLtransactions,youshouldsometimesuseSERIALIZABLE,sometimes
not-youhavetodecidefromcasetocase.YoucouldgoforSERIALIZABLE allthetime,butthen
yourtransactionswillbelongerandyouwillalsoaffectconcurrencymuchmorethanifyouusea
lowerTIL.
Note
ForagooddiscussiononhowtothinkabouttheTIL,seeTimEwald'sTransactional
COM+: Building Scalable Applications.
1
Avoiding 2PC with the Help of Replication
IfyouaregoingtousemorethanoneRMinyourtransactions,youhavetousedistributed
transactionsifyouliketohavetransactionsemanticsovertheRMs.Distributedtransactionswill
operatewiththe2PCprotocol,whichisveryexpensivewhenitcomestoperformancecomparedto
localtransactions.AnalternatesolutionistoonlyUPDATE oneoftheRMsandthenlettheUPDATE
affecttheotherRMwiththehelpofreplication.Youwon'tgetfulltransactionsemantics,butoften
theconsistencyisgoodenough.Inaddition,thedifferenceinthroughputcanbeverybig.Thiswill
alsoincreasethereliabilitybecauseifoneoftheparticipantsina2PCtransactionisdown,the
transactioncan'tbefulfilled.Withreplication,thetransactionswillstilltakeplace,evenifoneof
thedatabaseserversisnotoperatingatthetime.ThefaultyserverwillgettheUPDATEswhenit
comesbacktolifeagain.
Tips on Decreasing the Risk of Deadlocks
Forlocaltransactions,theRMwillquicklyfinddeadlocksitself.Inthiscase,theRMwilldecide
whichtransactionshouldlose,anditisinterruptedsotheothertransactioncancontinue.
Unfortunately,youcan'tcatchadeadlockerrorinyourstoredproceduresbecausetheyare
interrupted,andeventhebatchthatcallsthestoredprocedureisinterrupted.Theerrorwillalways
havetobecaughtinyourcomponentsinstead.
Fordistributedtransaction,theTransactionManager(TM)will,inmostindustrialimplementations,
notreallytrytodetectadeadlockbutwillratheruseatimeout.Ifthetransactiontakesmorethan
xseconds,theTMdecidesthatthereisadeadlockandthetransactionisinterrupted.Thecasewith
distributedtransactionsisworsebecauseitwilloftentakelongertodetectthepresumable
deadlocksituationand,meanwhile,severaltransactionsareblocked.It'salsocommonthatmore
thanonetransactionwillbeaffectedbythetimeoutandthereforebeterminated.
Inanycase,deadlocksarebadforourhealth,bothwithlocalanddistributedtransactions.We
can'tavoiddeadlockscompletely,butwecanmakethemlesslikelytoappear.Usingshort
transactionsisextremelyimportantforreducingthedeadlockrisk.Theshorterthetimeyouhold
thelocks,thesmallertheriskthatsomebodyelseacquiresthelocksinawaythatconflictswith
yours.ThefollowingareothertipsIrecommendfordecreasingtheriskofdeadlocks.
Taking UPDLOCK When Reading Before Writing
Anothercommonreasonforadeadlockisthattwotransactionsfirstacquiresharedlocksonone
andthesamerowandthenbothofthemtrytoescalatetheirlockstoexclusivelocks.Noneofthe
transactionssucceedsuntiloneofthetransactionsisinterrupted.Thesolutionissimple-whenyou
knowyouneedanexclusivelock,acquireitimmediatelyinsteadofstartingwithasharedlockthat
youlaterescalatetoanexclusivelock.YoucandothatwiththeUPDLOCK optimizerhintinSQL
Server,asshowninListing6.11.
Listing 6.11 Example of How to Acquire an Exclusive Lock with a SELECT
SELECT description
FROM errand (UPDLOCK)
WHERE id = @id
Working with Tables in the Same Order for All Transactions
Onesimpletipistoalwaysworkwithyourtablesinthesameorderinalltransactions.InListing
6.10,youseethatonerowisinsertedintoerrand andthenonerowisinsertedintoaction.That
ordershouldbeusedforalltransactions.I usuallysaythatthemastertableshouldbeusedbefore
thedetailtable.Youdon'thaveachoicewhenitcomestoINSERTsasinListing6.10becauseof
FOREIGN KEY constraints.Whenthisruledoesn'thelpbecausetherearenorelationshipsbetween
thetables,usealphabeticalorderforthetablenamesinstead.
Unfortunately,DELETE hastohappenintheoppositeorderofmasteranddetail,onceagain
becauseofFOREIGN KEY constraints.TheproblemiseasilysolvedbyfirsttakinganUPDLOCK onthe
masterrow,andthenDELETE thedetailrowsfollowedbyaDELETE ofthemasterrow.
Obscure Declarative Transaction Design Traps
UsingCOM+transactionsisoftenthoughtofasbeingsimplebecausethesystemwilldealwiththe
transactionsforyou,decidingwhentoCOMMIT andwhentoROLLBACK.Evenso,it'simportantto
understandhowCOM+transactionsworkandhowyoursettingswillaffecttheoutcome.
Allthreetrapsthatfollowaretakenfromsituationswheremyproposedarchitecturewasnotused.
Evenso,Iwanttopointoutafew"gotchassoyoudon'tfallintothesetrapsifyoudecidetouse
anotherarchitecture.
Example 1: Incorrect Error Trapping
Thereareseveralexamplesofhowerrortrappingmightgowrong,someobviousandsomenotso
obvious.TheresultmightbethatSetAbort() isn'tcalledatallandthatthetransactionwill
thereforebeCOMMITed.It'sprobablyobviouswhytheexampleinListing6.12isn'toneyouwantto
haveinyourcode.WhentheThrow() statementexecutesinListing6.12,themethodwillstop
executing(exceptforoneormorepossibleFinally and/orCatch blocksonanouterlevel).
Listing 6.12 Incorrect Example of Error Trapping
Catch e As Exception
Throw(e)
ContextUtil.SetAbort()
Example 2: Incorrect Use of NotSupported
Inthisexample,wewillletarootcomponentcontrolatransactionandaskforhelpfromtwo
secondarycomponentsfordoingsubtasks.AssumeyouhavethecomponentslistedinTable6.5.
Table 6.5. Example 2: Instances, Components, and Transaction Attributes
Instances and Components Transaction Attribute
aRoot (instanceofcomponentA)
TransactionOption.Required
aSecondary (instanceofcomponentB)
TransactionOption.Required
anotherSecondary (instanceofcomponentC)
TransactionOption.NotSupported
AssumethataRoot callsaSecondary andaSecondary UPDATEsaspecificerrand.(Recallthesample
applicationAcmeHelpDeskintroducedinChapter5.)ThenaRoot callsanotherSecondary that
SELECTsthesameerrand,oratleastanotherSecondary triesto.BecausecomponentCismarked
withNotSupported,itsinstanceswillnotexecuteinthesametransactionthatisstillgoingonfor
aRoot andaSecondary.Instead,theSELECT incomponentCwillbewrappedinanimplicit
transactionandputinawaitstatewaitingfortherowintheerrandtabletobereleased.Nowwe're
stuckinawaitstateuntilthetimeoutforthetransactionhelpsus.Unfortunatelyitwon't;itwillkill
us.
ThetypicalsolutiontothisproblemwouldbetouseSupported orDisabled forcomponentC.
(Disabled onlyworksifCinstancescanco-locateinthecontextsofAinstances.)Itcouldalsobe
thecasethataredesignneedstotakeplace.PerhapstheSELECT isn'treallyneededbecausethere
maybenoUPDATE TRIGGER forthetable.
Example 3: Incorrect Use of RequiresNew
Thethirdexampleisperhapsabitstrange,butisstillpossible.Thistime,wealsohavethree
instancesandcomponents.YoucanseehowtheyareconfiguredinTable6.6.
Table 6.6. Example 3: Instances, Components, and Transaction Attributes
Instances and Components 1ransaction Attribute
aRoot (instanceofcomponentA)
TransactionOption.Required
aSecondary (instanceofcomponentB)
TransactionOption.Supported
anotherSecondary (instanceofcomponentC)
TransactionOption.RequiresNew
Thistime,aRoot callsaSecondary andaSecondary SELECTsaspecificerrand.Becauseofthe
informationintheerrand,aRoot understandsthattheerrand mustbeupdated;therefore,itcalls
anotherSecondary sothatitcandealwiththeUPDATE.Nomatterwhathappensafterwardsinthe
activity,theUPDATE musttakeplaceandthereforeitisputinaseparatetransaction.Theresultis
thesameasforthesecondexample;anotherSecondary grindstoahaltandwaitsforthetimeout.
Asamatteroffact,IhaveneverusedRequiresNew inanyofthesystemsIhavebuilt.Theonly
timeIhaveeventhoughtaboutitwasformyerror-loggingcomponent,butasIsaidinChapter4,
"AddingDebuggingSupport,Iuseanothertrickinsteadforhavingtheerrorloggedinaseparate
transaction.Thesolutiontothisproblemmustbearedesign,butfirstwehavetoaskourselves
whetheritiscorrectatallthatCshouldruninatransactionofitsown.ThescenarioIpresented
herewas,asIsaid,abitstrange.
Traps Summary
AllthreeexamplesoftrapsthatIselectedanddiscussedherewereproblemsarisingwhenseveral
componentsinteracted.Noneoftheproblemswouldhaveexistedifallthecodehadbeenputina
singlemethodinstead.Butthatisnot themoralofthisstory.Thatwouldleadtocodebloat.
Instead,whatI'msayingisthatyoumustunderstandhowdifferentsettingsaffectyour
transactionsandbeverycarefulwitherrortrapping.Asusual,COM+transactionsdon'tchange
that.
Evaluation of Proposals
Asinpreviouschapters,it'stimeformetoevaluatetheproposalsIhavepresentedinthischapter
againstthecriteriaIestablishedinChapter2.
Evaluation of Transaction Technique Proposal
AsIsaidearlier,thetransactiontechniquetofavoristocontroltransactionsinthestored
procedures-thatis,whenyouonlyhaveoneRM.It'saverygoodideatoletthetransactionsbe
controlledinstoredprocedureswhenyouhaveaslownetworkbetweenthecomponentsandthe
database.Asamatteroffact,thisismostoftenthesolutionthatgivesthebestperformanceand
scalability.Whenversion1of.NEThasbeenreleased,youwillfindmyresultsofatestinwhich
storedprocedurecontrolledtransactionsarecomparedtoADO.NETcontrolledtransactionsand
COM+controlledtransactionsatthebook'sWebsiteatwww.samspublishing.com.
Productivitymightbebetterifyougoforautomatictransactionsinstead,becauseyoudon'thave
tocodethetransactionsyourself.Inreality,Ifindthedifferenceinproductivitybetweenautomatic
transactionsandlocaltransactionstobesmall.
Maintainability,reusability,andinteroperabilitymaybenegativelyaffectedifyoudon'talsoprepare
formovingtoautomatictransactionswhenyouneedto.Therefore,youshouldalsoconsiderusing
theproposalIevaluateinthenextsection.
Evaluation of Transactions in the Architecture Proposal
Myproposalofhowtodealwithtransactionsinthenewarchitectureistotallyindependentofthe
typeofconsumer,atleastaslongasyoulettheconsumerstayoutofthetransaction,whichis
definitelyhowitshouldbe.Theexceptiontothisiswhentheconsumerisacomponentthatuses
automatictransactions,butthatismoreamatterofinteroperability.Youshouldnotethatthe
transactiondesigninthearchitecturehasinteroperabilityasoneofitsmajordesigngoals.
WhenusingpureT-SQLtransactions,themostefficientsolutionfortransactionsisimplemented.
Thisisespeciallyapparentwhenthereisaslownetworkbetweentheapplicationserverandthe
databaseserver.
Boththeperformanceandthescalabilityfactorsaretargetedwellbytheproposal.Themain
problemisthatmethodsattheApplicationlayerclassesthatdon'tneedtransactionsmayalso
haveautomatictransactionsstarted.Thereisaneasysolutiontothis(splittingtheclassesinto two
parts),butitisnotgoodformaintainabilityandproductivity.
Apartfromthedrawbackjustmentioned,Ithinkthatthearchitectureproposalisgoodfor
maintainability,reusability,debuggability,andinteroperability.Toalargeextent,thisisbecauseof
thedataaccesspatternthatwillbediscussedinChapter8.
Bygettingshortertransactions,whichisoneoftheresultsofthearchitectureproposal,the
reliabilitywillalsoincreasebecausetheriskfordeadlockswilldecrease.
Evaluation of the Flexible Transaction Design Proposal
Themainreasonfortheflexibletransactiondesignistoincreasemaintainability.Becauseofthat,
reusabilityandinteroperabilityareimprovedaswell.Intheshortrun,itmightbethecasethat
productivityisdecreased,butinthelongrun,itwins.
Ifyouwantyourcomponentstoworkwellbothwithautomaticandmanualtransactions,youhave
to testbothsituationsthoroughly.Theproposeddesignwillincreasetestabilitybecauseitiseasy
tojustchangethesettingsforsomeattributesandtestagain.
What's Next
Therehasbeenalotoftalkaboutbusinessrulesinthelastfewyears,butrelativelyfewconcrete
recommendationsforhowtohandlethemhavebeenshown.Inthenextchapter,Iwilldiscuss
severaldifferentproposalsforhowtotakecareofbusinessrules,bothinservicedcomponentsand
instoredprocedures.
References
1.T.Ewald.Transactional COM+: Building Scalable Applications.Addison-Wesley;2001.
2.T.Pattison.Programming Distributed Applications with COM+ and Visual Basic 6.0.Microsoft
Press;2000.
3.J.GrayandA.Reuter.Transaction Processing: Concepts and Techniques.MorganKaufmann;
1993.
4.P.BernsteinandE.Newcomer.Principles of Transaction Processing.MorganKaufmann;1997.
5.G.Brill.Applying COM+.NewRiders;2000.
6.D.Platt.Understanding COM+.MicrosoftPress;1999
Chapter 7. Business RuIes
IN THIS CHAPTER
N AShortIntroductiontoBusinessRules
N LocationandSolutionsofBusinessRules
N ProposalforWheretoLocateDifferentBusinessRules
N Business Rules Tips
N EvaluationofProposal
Mostpublishedarchitectures,suchasWindowsDNA,recommendhavingatierforbusinesslogic,
orbusinessrules,andusingacomponentlayertohandlethisbusinesslogic.Yetrarelydoyoufind
recommendationsforhow tohandlebusinessrules.
CentralizingallbusinessrulestoasinglelayerintheBusinesstierhasitsmerits,suchashavingall
therulesinonelocation,but,asalways,it'samatterofgiveandtake.Whilethismaybebeneficial
formaintainability,itcanbecountertoscalability.Meanwhile,certainrulesmakemoresensein
thedatabasefromaperformance,scalability,andproductivityperspective.
Object-orientationisconcernedwithinformationhiding,whichisgoodforplacingandmaintaining
businessrulesduringthelifespanofthesystemandforquicklyandeasilydistributingandputting
anewbusinessruleintooperation.Mymainconcernsaboutbusinessrulesarestructureand
architecture.
Inthepreviouschapter,IsaidthattransactionsdrivethearchitectureIuse.Youcanalsothinkof
transactionsasaformofabusinessrule.Duetotheimportanceoftransactions,aswellasother
typesofbusinessrules,businessruleshaveamajorinfluenceonhowthearchitectureisdesigned.
Ihavenowtouchedbrieflyonwherebusinessrulesshouldbelocated.Intherestofthischapter,I
willdiscussthelocationofbusinessrulesindepthandwhatmyproposalis,namelytolocate
businessrulesintheBusinessorDatatier,dependingonwhatmakesmostsenseonacase-by-
casebasis.Iwillalsolookatdifferentwaysofconstructingtherules,dependingonthechosentier.
Afterthat,Iwilldiscussthelocationandimplementationofasetofdifferentexamplesofbusiness
rules.Finally,Iwilldiscusshowbusinessrulesmightbedealtwithinthefuture.BeforeIdoall
this,I'dliketostartwithashortintroductiontobusinessrules.
A Short Introduction to Business Rules
WhatdoImeanbybusiness rules? Thetermcanbealittlemisleading.WhenIrefertobusiness
rules,Iamreferringnotonlytovalidationsofrules-validationsareanimportantpart-butalsoto
ordinarylogic,suchascorealgorithms,transformations,compensations,calculations,and
authorizations.Perhapsyouprefertheterm"businesslogicorjust"logic.Eitherway,theterm
isn'timportant;it'swhatthetermreferstothatmatters.
Businessrulesapplynotjusttobusinesssystems,ofcourse.Businessrulescanbefoundin
technicalsystemsaswell(althoughdevelopersoftechnicalsystemsoftenreferto"domainand
"domainrulesinstead).Oneexampleofatechnicalsystemwithasetofbusinessrulesisacontrol
systemforanelevator.Inthisexample,thebusinessrulesmaydictatethattheclosestavailable
elevatorshouldansweranygivenrequest.Thus,whenthecontrolsystemisnotifiedthatsomeone
onthefirstfloorwantstogoup,itsendstheelevatorwaitingatthethirdfloorinsteadoftheone
attheninthfloortoanswertherequest.
Nomatterthecapacity,findinganddefiningbusinessrulesisanactivityforboththeclientandthe
developer.Hopefully,asthedeveloperyoucangettheclienttoconsiderreal-worldrules,leaving
youtofocusontechnicalrules,suchasuniquenessandtransactions,thattheclientprobablywon't
thinkabout.Thinkingaboutbusinessrulesisamajorstepintheanalysisanddesignactivitiesfor
buildingasystem.
Note
Althoughitisoftenusefultobreakdowncomplextopicsintocategories,Iamnotgoing
tocategorizethebusinessrulesinthischapterbecauseitwouldbetooabstract.There
aremanyclassificationstobeinspiredby,andnode-factostandardasIunderstandit.
Instead,Iwillgiveseveralpracticalexamplesofdifferentkindsofbusinessrules.For
moreinformationaboutcategorizations,seeRonaldG.Ross'sThe Business Rule Book,
Second Edition,
1
thechapteronbusinessrulesinJamesMartinandJamesJ.Odell's
Object-Oriented Methods,
2
orthechaptersonconstraintsinTerryHalpin'sInformation
Modeling and Relational Databases.
3
NowthatyouknowwhatImeanwhenIrefertobusinessrules,it'stimetogetdowntothenitty-
grittyofhowandwheretodealwiththem.
Location and Solutions of Business Rules
AsyourecallfromChapter5,"Architecture,IusethetiersandlayersshowninFigure7.1inmy
architectureproposalasastartingpointformanyapplications.
Figure 7.1. Tiers and layers in my proposed architecture.
Let'stakeacloserlookatthedifferenttierswecanchooseamongforlocatingthebusinessrules.
Locating the Business Rules in the Consumer Tier
WherecanIlocatemybusinessrulesinthisarchitecture?Let'sdecidethattheConsumertieris
notasuitableplaceforbusinessrules.Whynot,youmayask?Thereareseveralreasons:
N User Interface (UI) trends- ThereisanewUItrendeveryyear,andmanyorganizations
oftenadjusttothesenewtrends.Becauseofthat,theyhavetorebuildtheUI(andthe
Consumertier)orbuildyetanotherUI.
N UI applications- ThereareoftenseveraldifferentUIapplicationsforaspecificsetof
components.ThesamerulesmustapplyforalltheUIapplications.
N Decentralized consumers- Sometypesofconsumerswillbedecentralized(forexample,
WindowsFormsapplications)anditmightbedifficulttoforceanupdatetotakeplacefor
everyconsumeratthesametime.
N Not in our control- It'scommonthatanothergroupofdeveloperswilldevelopthe
consumer(s),andthisgroupwillnotbethesameasthegroupthatwilldevelopthe
BusinessandDatatiers.
However,althoughit'snotagoodideatolocatebusinessrulesintheConsumertier,itisagood
ideatocarryoutearlytests-suchasrequiredfields,formats,andsoon-intheConsumertierfor
scalabilityreasonssothatuseless,expensive,andresource-consumingroundtripscanbeskipped.
Still,thismightmeanmoremaintainabilitywork.Laterinthechapter,Iwilldiscussanapproach
thatallowsyoutohaveyourcakeandeatittoo.
Locating the Business Rules in the Business Tier
TurningtotheBusinesstier,thebasicideaoftheDomainlayerissimplythatitshouldtakecareof
genericbusinessrules.DoingthisispossibleintheApplicationlayerinstead,butthemorerules
thatcanbedealtwithintheDomainlayer,thebetter,becauseseveraldifferentApplicationlayer
classeswillusethesameDomainlayerclasses.AsIsaidinChapter5,theclassesinthe
Applicationlayeraremodeledfromtheusecases.TheclassesintheDomainlayermodelconcepts,
andthoseconceptswillbeusedbyseveralusecases.Theonlyrulesthatshouldbeconstructedin
theApplicationlayerarethosespecifictocertainusecasesonly,aswellasrulesforauthorizing
businessfunctionuse.
AsyoumightrecallfromChapter5,IlettheApplicationlayercallthePersistentAccesslayer
directlyforfetchmethods,butIcalltheDomainlayer(whichinturncallsthePersistentAccess
layer)forwritemethods.Businessruleslieatthecenterofthisdecision.Therearemostoftenno
businessrulesforthespecificfetchoperations,soacalltotheDomainlayerwouldtendtoresultin
justapassthroughtothePersistentAccesslayer.Theonlyvalueindoingthiswouldbetosimplify
theApplicationlayerandmakeitmoreconsistentsothatitisonlyawareoftheDomainlayer.Still,
thisadvantagedoesn'tmakeupforthedrawbacksthatcomefrommoreand"dumbcode.
AsIsaid,whentheApplicationlayerrequests awritemethod,thecallwillgototheDomainlayer
andtheDomainlayerthencallsthePersistentAccesslayer.Ofcourse,itwouldbepossibletolet
theApplicationlayerstillcallthePersistentAccesslayeraftertheDomainlayercallhasbeen
executed,butIfindmyproposaliscleaner.Inaddition,sometimestheDomainlayerneedstocall
thedatabasetofetchdatabeforeevaluatingtherule.TheDomainlayermightalsoknowmore
aboutwhatisneededfromthedatabaseinaspecificscenariothantheApplicationlayerdoes.With
thisdesign,theDomainlayermightalsodecidetogotothedatabaseortojustaddrowstothe
scriptthatwillbesenttothedatabaseattheendofthescenario,whichiscompletelytransparent
totheApplicationlayer.
ButwehaveonemorelayerintheBusinesstier.WhynotletthePersistentAccesslayerhandlethe
businessrules?Well,itcould,butithasanotherwell-definedpurpose,namelytoencapsulatethe
accessofthepersistencemechanism,suchasSQLServer.Inmyopinion,weshouldkeepthis
layerfreefrombusinessrules.
Note
EventhoughIuseSQLscriptsthatarebuiltbycallingthePersistentAccesslayerand
executedintheDatatier,IthinkofthoserulesasbeinglocatedintheApplicationlayer
andtheDomainlayerbecausethoselayersarethedrivingforcesandarethelayersthat
definetherules.
Locating the Business Rules in the Data Tier
ThenexttieristheDatatier,whichalsoisanappropriatetierforseveralbusinessrules.Firstof
all, youshoulddealwithasmanystaticrulesaspossible(whichtheapplicationusershouldn'tbe
abletoaffectbychangingdatainthedatabase)usingconstraints(checkconstraints,primarykeys,
andforeignkeys)anddefaults.Thetwostoredprocedurelayersarethenverysuitableforother
rules.Thus,thePrivateStoredProcedurelayerisbetterthanthePublicStoredProcedurelayerasa
locationforbusinessrulesforthesamereasonastheDomainlayerisbetterthantheApplication
layer.Thatis,thePrivateStoredProcedurelayerandtheDomainlayerwillbereusedmore.Asa
result,therewillbemoregenericcodeinthePrivateStoredProcedureslayerandintheDomain
layer.
It'sextremelyimportanttorememberthatifyoudecidetoputanyrulesintheDomainlayer,you
don'tletanybodyreusethePersistentAccesslayeroranylowerlayerbecausetheywillbypassthe
businessrules.Inaddition,theusersshouldn'tbegrantedrightsfordirectaccesstotablesinthe
databasenortostoredproceduresiftheydon'tspecificallyhavetohavethem.It'sbesttothinkof
theApplicationlayerastheentrypointforusers,evenifyouputall rulesinthedatabasefromday
one.Youmightneedtoaddrulesinotherlayersclosertotheconsumerintheapplication's
lifetime.
Note
Thereisalwaysariskthatanadministratorwillupdatethedatabasetablesdirectly
withoutusingthecomponentsorthestoredprocedures.Theriskthatheorsheis
violatingthebusinessrulesishopefullynotthatgreat,butit'sstillthere.Byusing
constraints,theriskisgone.
AssumingthatwedecidethatthetwomainplacesforprogrammedbusinessrulesaretheDomain
layerandthePrivateStoredProcedureslayer,let'stakeacloserlookattheadvantagesforeach
approachandhowtherulescanbeconstructed.
Locating the Business Rules in the Domain Layer
Forgeneric,concept-orientedbusinessrules,theDomainlayerisoftenaveryfittinglocation.Even
thoughtherearenumerouspossibilities,let'stakealookathowtherulescanbeimplemented
here.
How Business Rules Can Be Implemented in the Domain Layer
Let'sreviewthearchitectureinChapter5 toseehowtheclassesintheDomainLayerappear.In
Figure5.10 inChapter5,youcanseethedoErrand classandtheISaveable interfacethatthe
Applicationlayeruseswhencallingtheclass.
Location and Call Mechanism
Havingsaidthat,I'dliketostartthediscussionbylookingatwheretherulescheckislocatedand
howitcanbeaccessed.Anobvioussolutionforwheretolocatebusinessrulesistoletthe
doErrand.Save() methodhideacalltoaprivateCheck() method.Thisistoensurethatthe
providedDataSet ischeckedaccordingtothoserulestheclassisresponsibleforchecking.
Note
Becarefulthatyoudon'tmissthefactthatthedatacarrier(forexample,aDataSet)has
beenchangedwithscrollingbeforeyoustartcheckingtherulesagainsttheDataSet.
Also,becarefulthatyoudon'ttouchthestateofthedatacarrierbecausethereisother
codeexpectingthedatacarriertobeinaparticularstate.UsingClone() isoftenagood
idea.
Anotherwaytochecktherulesisthroughapublicinterface,forexampleICheckable,tobeused
betweenclassesintheDomainlayer.ThesituationthatshouldbesolvedhereiswhenaDomain
classneedsanotherDomainclasstocheckrulesonbehalfofthefirstclass.Iusedthissecond
approachalotinmyoldarchitecture(whichIdiscussedinChapter5 andChapter6,
"Transactions).Inthenewarchitecture,itislessuseful,becauseitistotallyacceptableforthe
ApplicationlayertomakeseveralcallstotheDomainlayerwithinthesametransaction.
Processing
WhenyouhavedecidedonthelocationandcallmechanismforyourbusinessrulesintheDomain
layer,it'stimetothinkabouthowtheyshouldbeprocessed.Asusual,thereareseveraloptions:
N Use ordinary VB code and check rule by rule- Norocketsciencehere.Thisisthemost
typicalandthe preferredwaytogoforrulesintheDomainlayer.
N Add rows to the SQL script with the help of the Persistent Access layer- Bydoingthis,you
actuallydelaytheexecutionofthechecksuntilthecompletescriptisexecutedagainstthe
database.Itisalsothedatabaseitselfthatdoestherealcheckingoftherules.
N Create another SQL script for retrieving data from the database (with the help of the
Persistent Access layer, as usual) and use those values to check rule by rule with ordinary
VB code- NotethatthissolutionpartlydefeatsoneofthemainpurposesoftheSQLscript
solutionbecausetherewillbeseveralroundtripstothedatabase.Additionally,ifyoukeep
locksatthereadrows,youmayseverelyaffectthroughput.
Note
Iwilldiscussthe patternofusingSQLscriptsforaccessingthedatabaseindepthin
Chapter8,"DataAccess.
Motivation for Using the Domain Layer for Most Business Rules
Asyouhaveprobablyunderstoodbynow,orcertainlywillunderstandasyoureadon,Idon'tthink
theDomainlayeristheonlysuitablelayertolocatebusinessrules,butitcertainlyhasitsmerits:
N Certain rules are much easier to write in Visual Basic .NET than in T-SQL- Amongother
things,thesophistication,productivity,comprehensiveness,powerfulness,flexibility,and
extensibilityaremuchbetterforVisualBasic.NETasalanguagecomparedtoT-SQL.
N Relational databases are very good at set-based processing, but not at row-by-row
procedural processing- Still,proceduralrulesarenotthatuncommonafterall.
N The processing will take place at a tier that is easy to scale out- Ihavementionedit
before,butit'sworthmentioningagain-thedatabaseishardertoscaleoutthanthe
applicationserver.Becarefulthatyoudon'tusetoomanyresourcesfromthedatabase
becauseofthis.Inthecaseofstaticchecks,whendatadoesn'thavetobefetchedfromthe
databaseforchecking therules,theDomainlayerisaveryefficientandsuitablelocationfor
rules.
Note
InChapter5,Imentionedthatfor"smallsystemsand"simplesystems,it'spossibleto
skiptheDomainlayer.IalsosaidthatthisappliedtothePrivateStoredProcedureslayer,
butI'mlessinclinedtoskipthePrivateStoredProcedureslayer.Inanycase,thinktwice
beforeyouskipeitherof theselayers.Prepareforfuturegrowthbythinkinglargebut
initiallybuildingsmall.
Locating the Business Rules in the Private Stored Procedures Layer
CertainrulesfitverywellinthePrivateStoredProcedureslayer,butbeforearguingthecasefor
whytheydo,Iwillstartwithadiscussiononhowtheycanbeimplemented.
How Business Rules Can Be Implemented in the Private Stored Procedures Layer
Asusual,Iprefertofactorouttherulesinspecificstoredprocedurestomakethecodemore
maintainable.ItypicallyaddaCheck suffixasthenamingconvention;thus,atypicalstored
procedurenameforrulesthatapplyonlyforUPDATE wouldbeErrand_UpdateCheck().
Iusetwodifferentstylesforbusinessrulesinstoredproceduresthatperformchecks.Assumethat
anUPDATE istobedone.YoucaneitherlettheUPDATE takeplaceandreadfromthedatabaseand
checktherulesbeforetheendofthetransaction,orcheckparametersandreaddatafromthe
databasetochecktherulesbeforemakingtheUPDATE.
Listing7.1showssamplecodeinastoredprocedureinwhichtheUPDATE takesplaceandthenthe
dataisreadbackfromthedatabase.InListing7.2,thesecondstrategyisshown;thatis,thedata
ischeckedwhenitcomesasparameterstothestoredprocedure.Inbothcases,theruleisthatthe
responsibleproblemsolvercannothavemorethan10openproblems.
Listing 7.1 Simplified Example of When the First Strategy Is Used for Checking a
Business Rule in a Private Stored Procedure
BEGIN TRANSACTION
UPDATE errand
SET responsible = @responsible, ...
WHERE id = @id
--Error trapping goes here.
IF @status = 1 BEGIN
IF (SELECT COUNT(*)
FROM errand
WHERE responsible = @responsible
AND status = 1) > 10 BEGIN
SET @anError = 82121
SET @anErrorMessage =
'Too many open errands for this problem solver'
GOTO ExitHandler
END
END
COMMIT TRANSACTION
Listing 7.2 Simplified Example of When the Second Strategy Is Used for Checking a
Business Rule in a Private Stored Procedure
IF @status = 1 BEGIN
IF (SELECT COUNT(*)
FROM errand
WHERE responsible = @responsible
AND status = 1) > 9 BEGIN
SET @anError = 82121
SET @anErrorMessage =
'Too many open errands for this problem solver'
GOTO ExitHandler
END
END
BEGIN TRANSACTION
UPDATE errand
SET responsible = @responsible, ...
WHERE id = @id
--Error trapping goes here.
COMMIT TRANSACTION
Note
AsyouseeinListings7.1and7.2,Ididn'tshowreal transactioncodeanderrortrapping.
Youwillfindalotmoreaboutthistopicin Chapter9,"ErrorHandlingandConcurrency
Control.
Asyouguessedit,bothstyleshavetheirmerits.TheadvantagetolettingtheUPDATE takeplaceis
thatitissimplerandmoreproductivetoprogrambecauseyoudon'thavetosendaroundalotof
parametersbetweenstoredprocedureswiththeonlypurposeofevaluatingrules.Ifyouuse
triggers(Idon't),anotheradvantageisthatyouchecktherulesaftertriggershavedonetheirjob,
whichcouldbegoodbecausethetriggersmighthavechangedthedata.
TheadvantageofcheckingparametersbeforestartingtheUPDATE isamatterofefficiency.You
don'tdoanUPDATE thathastobeROLLedBACK directlyafter.Another,moreimportant,efficiency
aspectisthatyougetshortertransactionsaswell.Itisalsoeasierandmorenaturaltoevaluate
rulesattransitionswiththisapproach.IfyoufirstperformtheUPDATE,youdon'tknowwhatthe
"beforevaluewaswhenyougotoseewhetherthetransitionwasacorrectone,andthereisalso
achancethatyouwillreducethelengthofthetransactionandthelockingtime.
Note
Wecouldhaveasimilardiscussionforthe.NETcomponents.Shouldonlythedata
container(aDataSet forexample)besentovertothevalidationmethod,orshouldjust
thevaluesneededbesentoverasparameters?Inthiscase,I'mmoreinclinedtoskip
fine-grainedparameters.
Why I Don't Use Triggers Anymore
Inthepast,Iusedtriggersalot-toomuchreally.Ilikedtheideaof
triggersandwroteverycomplicatedones.However,whenthetimecame
tomakechanges,itwasarealmess.Inaddition,it'sdifficulttotest
triggerscompletely,tofollowexecutionsequences,andtowrite
complicatedtriggerscorrectly.Writingset-safetriggerscanalsoleadto
slowexecution.(Thereareotherproblemsassociatedwithtriggers,too,
butI'llstophere.)
Becausetheonlywaytocallthedatabaseinmyapplicationsisthrough
storedprocedures,Idon'treallyneedtriggers.Asaresult,Ihavestopped
usingthemcompletely.
Motivation for Using the Private Stored Procedures Layer for Most Business Rules
EarlierIsaidthatanadvantageoflocatingbusinessrulesintheDomainlayeristhatitisharderto
scaleoutthedatabase.Asalways,advantagescanbecomedisadvantages.Duetoitbeingmore
difficulttoscaleout,thedatabaseismorecentralizedthantheapplicationserverbecausethe
databasewillbescaledoutlessoften.Thatwasasubtleadvantage.Let'stakealookatsomeother
moreobviousadvantagestolocatingbusinessrulesinthePrivateStoredProcedureslayer:
N There are more reuse levels- Ifyouwantto,youcanreusealllayersinboththeBusiness
tierandthe DatatierifyouplaceallyourbusinessrulesinthePrivateStoredProcedures
layer.IfyouplacesomerulesintheDomainlayer,youcanonlyreusetheDomainlayerand
theApplicationlayer.
N It is easier to work with data- Businessrulesareoftenaboutdata.Databasesaregoodat
workingwithdata.
N It is more efficient for certain rules- Forexample,thisistruewhenyouneedtoreaddata
firstfromthedatabase.Thisway,youreduceroundtripsbetweentheapplicationserver
anddatabaseserver.Youalsoreducetheamountofdatathatissentoverthenetwork.
(However,mydataaccesssolutionmakesthislessofanadvantage.)
N Redeployment is easy- Itiseasytoredeployastoredprocedurewhenarulehaschanged.
ThebiggestdrawbacktoputtingtherulesinthePrivateStoredProcedureslayeristhelackof
portabilitybetweendifferentdatabaseproducts.AsIsaidinthePreface,youmustdecidewhat
yourfirstpriorityis-whetheritistocreateanapplicationthatisportablebetweendifferent
operatingsystems,typesofmachines,anddatabaseproducts,orwhetheritistousethechosen
platformaseffectivelyaspossible.Youcan'thaveboth.Ihavedecidedtooptforthelatterinthis
book.
Proposal for Where to Locate Different Business Rules
Wehavenowdiscussedtwotypicallocationsforbusinessrules-theDomainlayerandthePrivate
StoredProcedureslayer-aswellastheiradvantagesanddisadvantages.So,whichoneshouldyou
choose?Asalways,theanswertothequestiondependsonmanyfactors.Furthermore,Ihave
alreadyrecommendedthatyoulocatecertainrulesintheApplicationlayer,thePublicStored
Procedureslayer,oronthebasetables(forexample,withthehelpofconstraints).Togiveyoua
feelingforhowIchoosewhereto placebusinessrules,Iwilldescribesometypicalrulesinthenext
sectionandthenexplainwhereIwouldlocateandhowIwouldimplementthoserules.
Location of Business Rules in Classic Windows DNA
Inmyopinion,ClassicWindowsDNApreferstoputthebusinessrulesinthecomponentsrather
thaninthedatabase.StenSundbladandPerSundblad,intheirbookDesigning for Scalability with
Microsoft Windows DNA,
4
recommendplacingbusinessrulesinbothlocations.Theauthorsdiscuss
afewbusinessrulesandrecommendthatthosethatcaneasilybedealtwithbydeclarationsinthe
databasebehandledthatway.Anexampleisuniquenessrequirementsthatcaneasilybedealt
withbyaPRIMARY KEY,UNIQUE CONSTRAINT,orUNIQUE INDEX.Iagreewiththisapproach.
However,whenitcomestomorecomplexbusinessrules,SundbladandSundbladaremorepro-
componentsinsteadofpro-storedproceduresthanIam.
AsIsaid,theeasyanswertowheretoplacethebusinessrulesfortheexamplesIwillshowinthe
nextsectionisthatitdepends.Isthereaslownetworkbetweencomponentsanddatabase?Are
therealready10applicationserversinplace?Willthereonlybe15simultaneoususers?Theseare
thekindofquestionsyoumustaskyourselfasyoumakeyourdecision.WhenIdiscussmymain
proposalsformyexamples,Iwillassumeabasicsituationinwhichthereisonedatabaseserver
andoneapplicationserver,andthatthenetworkbetweenthedatabaseserverandtheapplication
serverisfast.Iwillalsoassumethatthereare50concurrentuserswith,say,oneminuteof
thinkingtime.Iwillalsokeepinmindthattheremaybemoreusersinthefuture.
Note
Thiswas,ofcourse,aloosedefinitionofthesituation.Asyouknow,it'spossibletokill
almostanyconfigurationwithfiveusers,whilethesameconfigurationmayalsobeable
tohandlehundredsofusers.Itdependsonthetasksthathavetobedoneandhowthey
areaccomplished.
Pleasenotethatyoushouldconsidermydiscussionthatfollowsonlyasaselectionofexamples-
it'snotexhaustivebyanymeans.Havingsaidthat,let'sreviewthedatabasemodelshownin
Figure5.2 inChapter5 fortheAcmeHelpDesksampleapplication.Asyouseeinthatfigure,there
aretwotablesnamederrand andaction.Inerrand,theproblemisdescribed,andinaction,the
attemptstosolvetheproblemaredescribed.Nowlet'slookatthefirstbusinessruleexample.
Business Rule Example 1: When an Errand Is Deleted, All Its Actions
Should Be Deleted Too
OftenIprefernottodeleterowsfromthedatabasebutinsteadtomarkthemasdisabledtonot
losethelog,sotosay.Sometimesyouneedtodeleterows.Ifthisisthecaseforthemasterrows,
you alsowanttodeletethedetailrows,ifany,soreferentialintegrityisn'tviolated.
BecauseSQLServer2000supportsdeclarativecascadingdeleteforforeignkeys,itisacandidate
techniqueforhandlingthisbusinessrule.However,IprefertoprogramtheruleinthePrivate
StoredProcedurelayerinsteadbecauseyoumayalsowantto"intercepttheDELETE ofthedetail
rowsandperformsomethingelsebeforetheDELETE takesplace.MyproposalistousethePrivate
StoredProcedureslayerforthisrule.
Business Rule Example 2: It Should Be Impossible to Report an Errand
with an Unknown Category
Asyouwillsee,Iamnotgoingtoincludeanexampleofabusinessrulewhereuniquenessis
required.Ithinkit'sobviousthataconstraintshouldbeusedinthedatabase.Youmayfindit
obviousthatadeclarativeforeignkeyshouldbeusedforExample2too-thatis,itshouldn'tbe
possibletoreportanerrandwithanunknowncategory.However,I'mstillusingitasanexample
becauseI'dliketopointouttheneedtosaveincompleterowstothedatabasewithoutbreaking
foreignkeysconstraints.Tosolvethisproblem,Ioftengivedefaultvaluestotheforeignkeysso
thattheypointtoadummyrowintheirparenttable.Simple,butefficient,andforwidetables
wheretheuserhastoenteralotofinformation,thisisanespeciallygoodsolution.
Business Rule Example 3: The Price Calculation of the Errand Should
Depend on a Complex and Dynamic Discount Model
Assumethatthecustomerwhoreportsanerrandmustpayfortheworkontheerrand.Iftheuser
hashadacertainamountoferrandsinthelastmonth,heorshegetsaspecificdiscount.Thereare
severalintervalswithdifferentdiscounts.Differentcustomersalsohavedifferentdiscount
agreements,and differenterrandcategoriesalsogivedifferentprices.(Thesewerejustafew
examplesofthefactorsusedforcalculatingtheprice.)
Inthissituation,IprefertohavethecalculationinthePrivateStoredProcedurelayerbecausealot
ofdatamustbe readfromthedatabasebeforethecalculationcanbeexecuted.Anothergood
approachcouldbetohavethecalculationintheDomainlayer,especiallyifallthenecessarydata
formakingthecalculationcouldbecached.However,therearedrawbackswiththistypeofcaching
thatmakemereluctanttosaythatthisismybasicrecommendation.
Note
Iwilldiscussthemeritsandproblemsofserver-sidecachinginChapter8.
It'squitecommonthatyouwanttheconsumertodoaprecalculationwithoutmakingaroundtrip
totheApplicationlayer.Forexample,assumethatauserisplacinganorder,addingonearticle
afteranother.Heorshewouldprobablyfinditusefultoseehowmuchthepriceisaftereach
articleisadded.Formostsituations,thesameresultmustbereachedinboththeongoingandfinal
calculations;otherwise,theuserwillgetupset.
Howshouldyoudealwiththis?Thesimple solutionisnot todealwithit,buttoallowtheretobea
roundtrip.(InthecaseofclassicWebapplications,thisisnotaproblembecauseyou,asauser,
normallyexpectaroundtriptotheserverforthecalculationtotakeplace.)Ifthissolutionisnot
goodenough,youcanlettheconsumerreusethesamecomponent.Evenbetter,youcanleta
calculationcomponentserializeitselffromtheBusinesstiertotheConsumertierwith.NET.
Note
Atthetimeofwriting,lettingacalculationcomponentserializeitselffromtheBusiness
tiertotheConsumertierin.NETisatechniqueIhaven'texplored.
Business Rule Example 4: An Errand Must Be of a Correct State
Statuscodesareextremelycommoninworkflowapplications.Therulethatanerrandmustbeofa
correctstatecouldmeanseveraldifferentthings,mainlydependingonhowthestateismarked.If
itisasinglecolumn,itissimpletocheckthatthecolumnhasavalidvalue,forexample,withthe
helpofaforeignkeytoalookuptableor-moretypicallybecausethisissysteminformationrather
thanuserinformation-asacheckconstraintinthedatabase.
Thestatecouldalsobeunderstoodfromthevaluesofseveraldifferentcolumns.Forexample,ifa
coupleofcolumnshavehadvalues,thestateoftheerrandis,atanyrate,"reported.Ifthe
closedby columnhasavalue,theerrandis,atanyrate,"closed,andsoon.Inthiscase,therule
willbetodeterminethestateofaspecificerrandratherthantocheckthatitisacorrectstate.
Becauseseveralstaticrulesdifferbetweendifferentstatuses,usingacolumntomarkthestatusof
arowmakesotherruleseasiertoimplement.
Business Rule Example 5: An Errand May Only Transform from One State
to a Specific Set of Other States
Let'sassumethatthereisawaytoeasilydeterminethecurrentstateofanerrand,suchasa
status columninthetable.Ifthereis,it'squiteeasytocheckwhatthecurrentvalueisandwhat
thenewvalueis.IfyouuseDataSetsforholdingdata,youhaveboththenewandtheoriginal
valuethereallthetime.Inthecaseofstoredprocedures,youcanreadtheoldvaluefromthe
databaseandcheckthenewvalueasaparameterbeforeyousave.
Ihaveahardtimedecidingonwherethiskindofruleshouldbelocated.ThelasttimeIbuilta
systemwitharulelikethisIhadtherulelocatedinastoredprocedure.Theadvantagesofthe
Domainlayersolutionisthatyoudon'tdisturbthedatabasewhenthereisnouseindoingso,and
youdon'tmakeareadfromthetable.Theadvantageofthesolutionofputtingtheruleinthe
PrivateStoredProcedureslayeristhattheruleisclosertothedatabasesoyougetmorepossible
reuselayers.IprefertohavetheruleintheDomainlayer.
Business Rule Example 6: Only Users of Specific Groups May Use a
Specific Business Function
Whenyouwantonlyusersofspecificgroupstouseaspecificbusinessfunction,COM+role-based
security(RBS)isonewaytogo.Youcanbuildasimilarmechanismwithcommonlanguage
runtimecode accesssecurity,butIthinktheCOM+variantiseasiertouseandautomaticallymore
inaccordancewithitsbasicpurpose.Nomatterwhichversionyouchoose,Iprefertohavethis
businessruleimplementedintheBusinesstierinsteadofintheDatatier.Thismakesforoneless
task(andaprettylargeonetoo)forthedatabasetohandle.Thisrecommendationisespecially
clearifthealternativeistolettheenduserslogintothedatabasewithpersonalaccounts.That
approachisascalabilitytrapbecauseconnectionpoolingwon'tbeabletoreuseconnections
betweenusers.
Buteveniftheuserwon'tuseapersonallogontothedatabase,thestoredproceduresaren't
businessrelated.Thegranularityismuchsmallerifyoucomparethestoredprocedurestothe
Applicationlayerclasses,andthereisnonaturalmappingbetweenusecasesandstored
procedures.
Note
Unfortunately,inversion1of.NET,COM+RBSdoesn'tworkover.NETRemoting,only
overDCOM.Inmyopinion,thisisthemostimportantcaveat regardingthe.NET
integrationofCOM+.
Business Rule Example 7: You May Only Read Your Own Errands
Thisruleissecurityrelated.IjustsaidthatIlikeCOM+role-basedsecuritytomoveoutthe
securityissuesfromthedatabasesotheuserdoesn'thavetologontothedatabasewitha
personalaccount.Thankstothis,securitycheckscanbedefinedinaccordancewithaflexibleand
prebuiltmodel.
However,COM+role-basedsecurityonitsowncan'tsolveusersbeingabletoreadonlytheirown
errands.Inthisparticularcase,Iactuallyprefertohavethecheckinthestoredprocedureinstead.
Idon'twanttosendtherowsfromthedatabasetotheBusinesstierfornoreasoniftheusertries
toreadarowthatheorshedoesn'town.Adrawbackwiththeapproachofhavingthischeckinthe
storedprocedureisthatyouhavetosendtheuserId asaparametertothestoredprocedure.
Note
ForthesolutionofsendingauserId asaparametertothestoredproceduretoworkat
all,eachrowhastobemarkedwiththeownerinacolumn.Thisistruenomatterwhere
youdecidetoplacetherule.
Business Rule Example 8: Every Action Must Be "Stamped" with Date,
Time, and UserId for the Reporter
Thisisasimpleyetcommonrule.Inmyopinion,themosttypicalplacetohandlethisisinthe
PrivateStoredProcedureslayer.YoucanuseaDEFAULT inthetabledefinitionforthedateandtime
byusingtheGETDATE() function;forthecreatedby,youhavetoprovideaparametertothe
storedprocedureinstead.
Business Rule Example 9: When an Errand Is Closed, an Action Should Be
Created
Thisruleisanexampleofacompensatingrule."Ifthishappens,thisshouldalsohappen.Youmay
wonderwhatthereasonintherealapplicationisforarulelikethis.Assumethatiti spossibleto
reopenanerrandthatwasincorrectlyclosed.Thenitisveryinterestingtohavetheactionrowasa
logtoseethattheerrandhadbeenclosedearlier.
Inmyopinion,thisexampleisatypicaloneforwhentohandlebusinessrulesinthePrivateStored
Procedureslayer.Thedrawbackisthatyoumighthavetoprovideafewmoreparameters(someof
whichmightseemunrelatedtotheonlytaskyouthinkyouaredoingasaconsumerofthestored
procedure).
Business Rule Example 10: Every Errand Must Be Marked with a Unique
Running Value
Itisverycommonthatyouneedarunningvalueofsomekind,suchas:
1, 2, 3, 4, 5...
or
N20011031-0001, N20011031-0002, N20011031-0003, N20011101-0001...
Thefirstexampleisacommonincrementingnumericseries,whilethesecondexampleisaseries
usingaprefixandthedatewithanincrementingnumericseries.Thefirstexamplemightbethe
primarykeyofthetable,butIprefernottousethesecondexampleforthatpurpose.Inanycase,
iftheseriesisusedforlettingusersassociatetoacertainrow,Iprefernottohaveitasthe
primarykeybutonlyasanalternatekey.(Nowadays,myfavoriteprimarykeydatatypeisthe
UNIQUE IDENTIFIER,asItoldyouinChapter5.)
Tocreatethefirstseries,youcanuseanIDENTITY togetthatfunctionalityautomatically,oryou
canrollyourownsolution,typicallyinastoredprocedure.Forthesecondexample,youhaveto
builditonyourown.BecauseIhavetoconsiderearliervalues,forscalabilityreasonsandfor
shorteninglocks,IprefertodealwiththistaskinthePrivateStoredProcedureslayer.
Why Scalability Won't Suffer If You Execute
Logic in the Database
I'vehearditsaidmanytimes-particularlyfromOracleexperts-thatthe
databaseshouldn'tbeusedforlogic,atleastnotifascalableapplication
isrequired.Themainargumentisthatitisdifficultandexpensivetoscale
outthedatabase,soitshoulddoaslittleaspossibletoavoidbecominga
bottleneck.
Inmyopinion,thereisoneproblemwiththeirreasoning.Ifthedatahas
tobefetchedanywayforevaluatingasimplerule,itwon'thurtscalability
tostayinthedatabaseandevaluatetherulethereinsteadoftransporting
thedatatothecomponentsandthenevaluatingtheruleinthe
components.Asamatteroffact,itwilloftenbebetterforscalability.The
sameworkmustbedoneinbothcasesforthedatabase.Inonecase,the
datamustbetransportedtothecomponents,andintheothercase,there
willbeasingleIF clause.Youwilltypicallyalsogetshortertransactionsif
rulesliketheseareevaluatedinthedatabasebecauseyouoftenhaveto
holdalockbetweenthecheckandthewritingtothedatabase.Ifyou
havetotransportthedatatotheBusinesstier,thatlockwillbekept
longerandthroughputmightsuffer.
Theyarerightthatthedatabasewillbecomeabottleneckfasteriftherule
isevaluatedinthedatabase,butthatisjustbecausemoreuserscanbe
servicedwithoneapplicationserverandonedatabaseserver.Ifthe
databasedoesnothingelsethanperformpureSELECT,UPDATE,INSERT,
andDELETE statements,therewillprobablybemuchmoreroundtrip
utilizationsothattheroundtripscanbethefirstbottleneck.Alternatively,
theapplicationservermustbeclonedtobeabletostressthedatabase
enoughtomakethedatabasebecomethebottleneck.Whenthedatabase
finallybecomesthebottleneckinthiscase,youhaveperhapsfour
applicationservers(butitmightalsobesothatthereisanother
bottleneckbeforethedatabasebecomesoneinthiscasetoo).Theonly
problemisthatthenumberofservicedusersisthesameaswithone
applicationandonedatabaseserverwhenthedatabaseisexecutinglogic
tooandnotonlypureSELECT,UPDATE,INSERT,andDELETE statements.
Whichsolutiondoyoufindmorescalable?
Business Rule Example 11: The Date for Closing May Not Be Earlier Than
the Report Date
Thisisanexampleofarelationshiprulebetweenproperties.ItisatypicalexampleofarulethatI
thinkshouldbedealtwithasacheckconstraint,butusingtheDomainlayerhereisalsoagood
solution.
Note
Onedrawbackwiththedeclarativeapproachforcheckingrulesisthatitisharderto
determinewhattheproblemis.Yougetagenericerrorcodefromthedatabase,andyou
havetoparsetheerrordescriptionifyouwanttofindoutmoreaboutwhattheexact
problemis.Thisisactuallyareasonwhyhandlingthetaskinstoredprocedurescouldbe
betterthanaconstraint.
Business Rule Example 12: Those Responsible for Solving Errands May Not
Have More Than Ten Open Errands
Thisexampleissimilartothepreviousone,butthistimetheruleisnottobeevaluatedwithina
certainrow,butratherbetweenrows.Eveninthiscase,Iprefertodealwiththeruleasacheck
constraint(orpossiblywithaprivatestoredprocedure).Remember,moredatamustberead.
Tosolvetheproblemasacheck constraint,youcanuseascalaruser-definedfunction.SeeListing
7.3foranexampleofasolutionandListing7.4foranexampleoftheconstraint.
Listing 7.3 Scalar UDF for Solving Example 12
CREATE FUNCTION OpenErrandsForProblemSolver
(@problemSolverId UNIQUEIDENTIFIER)
RETURNS INTEGER AS
BEGIN
DECLARE @theReturn INT
SELECT @theReturn = COUNT(*)
FROM errand
WHERE responsible = @problemSolverId
AND status = 1
RETURN @theReturn
END
Listing 7.4 Constraint for Solving Example 12
([dbo].[OpenErrandsForProblemSolver]([responsible]) < 11)
Business Rule Example 13: Only Solve an Errand If the Reporter Has Paid
His or Her Membership
Onceagain,youhavetoreadinformationfromthedatabasetodecidewhetherthisrulehasbeen
metornot.Asusual,whenthisisthecase,IprefertodealwiththeruleintheDatatier,ifpossible
asacheckconstraint,orotherwiseinthePrivateStoredProcedureslayer.
Business Rule Example 14: Closing Date May Not Be Later Than Today
Inaway,thisisastaticruletocheck.Today'sdateisnotstatic,butnouserneedstoadjustitat
intervals.Thedateiswellknown,sotosay.Inthiscase,IthinktheDomainlayeristhebestplace
fortherule,butacheckconstraintorthePrivateStoredProcedureslayerworkswelltoo.
Whenitisamatteroftimeinsteadofdates,itisimportanttoseethatyouhaveyourmachines
time-synchronized.Ofcourseyoudon'talwayshavecontroloftheclientmachines,whichisa
problemworththinkingmoreabout.Youcan easilycheckthattheclienthasn'tcheatedwiththe
timeifyoulettheclientsetthetime.Ontheotherhand,ifyoudothis,thereisnorealpointto
lettingtheclientsetthetimeinthefirstplace.Yetanotherrelatedproblemisthatofthedifferent
timezones.It'softenagoodideatostorealltimesinGreenwichMeanTime(GMT)/UniversalTime
Coordinate(UTC)format.
Business Rule Example 15: It Should Be Impossible to Categorize Too
High a Level of "Seriousness" Depending on the Customer Contract
Ijustdiscussedastaticrule,butthisexampleisdependentondynamicinformationthattheend
usershavetheoptionofchanging.Yes,you'veguessedright:Ithinkthisrulebelongsinthe
database,ideallyinthePrivateStoredProcedureslayer.
Business Rule Example 16: It Should Be Possible to Create a Halfway -
Solved Errand Because of an Automatic Alarm
ThisisanexampleofwhenIcreateaninstanceoutofadynamictemplate.Whenthealarm
sounded,acoupleofactionsweredoneautomaticallybythesystemandthoseshouldbeloggedas
actionsinthedatabase.Thetemplatecouldbekeptinatableinthedatabase,anditwillbemost
efficienttodealwiththisruleinthePrivateStoredProcedureslayer.Thereisthennoneedtosend
alotofdatafromtheBusinesstiertotheDatatier.
Note
Althoughthisexampleisabitstrange,theconceptiscommoninreal-worldapplications.
YoumaythinkthatI'mtoofondofputtingrulesintheDatatier.Therefore,Iwillendthissection
bygivingyouatypicalexampleofarulethatIdon'tthinkbelongsinthedatabase.
Business Rule Example 17: Calculate Priority of Errand in Accordance with
Complex but Static Rules
Ihavealreadyindicatedso,buthereIgoagain.Itisbettertohavecomplexalgorithmsthatdon't
needmoreinformationfromthedatabasetoexecuteintheDomainlayerthaninthePrivateStored
Procedureslayer.I'mveryadamantaboutthis.It'sjustsomucheasiertowritethiskindofcodein
VisualBasic.NET,forexample,thaninT-SQL.
Summary of Business Rules Examples and Solutions
Table7.1summarizestheexampleswejustdiscussedandthesolutionsIrecommended.Keepin
mindthatwhenitisnecessaryfortheusertodynamicallyupdatethevaluesagainstwhichthe
ruleswillbechecked,thedatabaseisthebestplacefortherules.Inaddition,forstaticvalues,the
Domainlayerorconstraintsinthedatabasearethebestoptions.Finally,ofcourse,Imayhave
chosenexamplesthataremoredatabase-centricthaniscommon foryourapplications.Inany
case,IthinktheseexamplespaintaclearpictureofmyreasoningwhenIdecidewheretoputa
rule.
Table 7.1. Business Rule Examples and Recommendations
Business Rule
Aumber Description
Recommended Location for the
Most 1ypical Situation
1 Whenanerrandisdeleted,allitsactionsshouldbe
deletedtoo.
PrivateStoredProcedures
layer
2 Itshouldbeimpossibletoreportanerrandwithan
unknowncategory.
Databaseconstraint
3 Thepricecalculationoftheerranddependsupona
complexanddynamicdiscountmodel.
PrivateStoredProcedures
layer
4 Anerrandmustbeofacorrectstate. Databaseconstraint
5 Anerrandmayonlytransformfromonestatetoaspecific
setofotherstates.
Domainlayer
6 Onlyusersofspecificgroupsmayuseaspecificbusiness
function.
Applicationlayer
7 Youmayonlyreadyourownerrands. PrivateStoredProcedures
layer
8 Everyactionmustbe"stampedwithdate,time,and
userId fortheReporter.
PrivateStoredProcedures
layer
9 Whenanerrandisclosed,anactionshouldbecreated. PrivateStoredProcedures
layer
10 Everyerrandshallbemarkedwithauniquerunning
value.
PrivateStoredProcedures
layer
11 Thedateforclosingmaynotbeearlierthanthereport
date.
Databaseconstraint
12 Thoseresponsibleforsolvingerrandsmaynothavemore
thantenopenerrands.
Databaseconstraint
13 Onlystarttosolveanerrandifthereporterhaspaidhis
orhermembership.
Databaseconstraint
14 Closingdatemaynotbelaterthantoday. Domainlayer
15 Itshouldbeimpossibletocategorizetoohighalevelof
"seriousnessdependingonthecustomercontract.
PrivateStoredProcedures
layer
16 Itshouldbepossibletocreateahalfwaysolvederrand
becauseofanautomaticalarm.
PrivateStoredProcedures
layer
17 Calculatepriorityoferrandinaccordancewithcomplex,
butstaticrules.
Domainlayer
Business Rule Tips
Inthissection,IwillsharewithyousomemiscellaneousanddisparatetipsIhavecollectedrelated
tobusiness rules.
Tip 1: Define Once, Execute at Several Tiers
ThemostimportantplacetohaveyourdatacheckedforconsistencyisattheDatatier,sothat
integrityofthedataisprotectednomatterwhat.Severaltestsareeasilyaddedwithconstraintsin
thedatabase;evenso,itwouldoftenbebeneficialtocheckthesamerulesinearlierlayersaswell.
Ifwestartduplicatingtherulesfordifferenttiersbyhand,wearecreatingamaintainability
nightmare.
Atthetimeofwriting,Ihaven'texperimentedmuchwiththisidea,butIthinkitcouldbewiseto
dragoutinformationfromthedatabaseaboutthebusinessrulesthere,createIntermediate
Language(IL)codeon-the-fly,andexecuteitwiththehelpofEmit() in.NET.(Ihavepreviously
thoughtaboutsolutionsotherthanEmit(),suchasusingVBScriptorJavaScript,butInowthink
Emit() ofIListhemostpromisingsolution.)ThecodecouldexecuteintheBusinesstier,saving
someuselessroundtripstothedatabase.Evenbetter,letthecodealsoexecuteintheConsumer
tier,andskipevenmoreuselessroundtrips,andtheuserwillgetasnappierusabilityexperience.
Youshould,ofcourse,notre-createtheILateverycall,butratherperiodically,forexample,once
aday.
Atfirst,thesimplerules, suchasthosefoundasconstraints,canbedraggedoutfromthe
database.Youcanthenalsodragouttherulesputinstoredprocedures,butthatprobablyrequires
quitealotofwork,aswellasalotofdisciplineandcodingconventionswhenyouwritethestored
proceduresinthefirstplace.Finally,youcould,ofcourse,alsodragoutdatafromthedatabase
thatisusedforevaluatingagainst.
Thissoundsprettycool,doesn'tit?Andit'sgreatforscalability,butonceagain,makesureyou
haveyourbusinessrulesfirmlyimplementedatoneplacefirstbeforeyouevenstartthinkingabout
thisidea.
Inaway,DataSetssortofprovidesyouwiththiseffect,becauseitishereyoucanstatethat
relationshipsbetweenmasteranddetailrows(intheformof DataTables),andtheDataSet will
evaluatethoserulesforyou.(AssumeaDataSet containsinformationaboutdogsandowners.Ifa
ruleisthatadogmusthaveanowner,sucharulecanbe"toldtotheDataSet soit'snotpossible
toadddogstotheDataSet withouthavingcorrectownersforthosedogs.Thatis,thereferential
integritycheckcanexecuteattheConsumertiertoo,asanearlytest.)
XMLSchemaDefinitions(XSD)isatechniquethatyoucanusemanually,sotospeak,toeasily
havesomerulesevaluatedatseveraltiers.Thebuilt-inoptiontouseregularexpressionsin.NET
couldalsobehelpfulifyouwanttowriteagenericruleengineforwhichtherulescanbemoved
betweentiers.
Tip 2: Deal with Warnings Only
Ifyouliketojustgiveawarningbacktotheuser,itcanbeabittricky-thatis,raisingan
exceptionwithThrow() isn'ttherightmechanismforwarningsonly.Forexample,justthinkabout
whatwarningexceptionswoulddototransactionalclassesthatusetheAutoComplete() attribute.
Abetterapproachistouseacustommethodtosignalthewarningtotheuser.
Tip 3: Report Several Problems at Once
HaveyoueverfilledinformsattheWebandhadanerrormessage,takencareoftheproblem,had
anothererrormessage,andsoon?I'llbetyouhave.I'malsosureyouhatedthis.Sometimes,itis
niceforusersiftheyaregiveninformationaboutalltheerrorstheyhavemadeinonemessage.
Forthattowork,youcan'tstopcheckingrulesjustbecauseyoufindaproblem.Instead,youhave
togothroughalltherules.Forrulesinstoredprocedures,youcanmakeaRAISERROR() forevery
problem,havethecomponentstheniteratetheerrorscollected,andproduceoneexceptionto
sendbacktotheenduserwiththeinformationaboutalltheproblems.
Whenitcomestohavingrulesinthecomponents,youjustgatheralltheinformationandthen
raisetheerrorafterwardswithThrow() insteadofaftereveryproblem.Youdon'thavetodoitthis
way,butit'sthemostdirectapproach.
Asamatteroffact,thisisaverygoodsituationforusingacustomexception,sothatyouletthe
exceptionhaveacollectionwithoneitemforeachbrokenbusinessrule.
Tip 4: Remember Which Layer Is Responsible for Displaying Error
Messages to the Users
I'veseenerrormessagesinthestoredproceduresbeingusedintheconsumertierinerrordialogs
morethanonce.(ThisactuallymeansthatIhavedoneitmyselfseveraltimes.)Understress,this
isacommonmistakebut-asIsayintheheading-rememberthat itistheConsumerHelper
layer'sresponsibilitytoproduceuser-friendlyerrormessagesfortheendusers.Theerror
messagesyouuseinthestoredproceduresareforprogrammers.
Note
InChapter9,Iwilldiscusserrorhandlinganderrormessagesingreaterdetail.
Evaluation of Proposal
Theevaluationofmyproposalisdifferentinthischapterthaninotherchapters.Inthischapter,I
willonl
ofcourse,theDatatiercanbeabadchoice,suchaswhencomplexcalculationsandprocedural
algorithmsareused.
PerformanceandscalabilitywilloftenwinifthebusinessrulesarelocatedintheDatatier,but,as
always,thisisn'talwaysthecase.Onceagain,watchoutforcomplexcalculations,forexample.A
largepercentageofalltransactionsmayalsofailbecauseofbrokenbusinessrules.Itwouldthen
beabaddecisionforscalabilityandperformancetotravelallthewaytotheDatatiertodiscover
thisifitcanbedeterminedearlier.
UsingtheDatatierforbusinessrulesisgreatforreusabilitybecauseyouwillgetmorereuse
layers.Ialsothinkitisverygoodforreliabilitybecausethereislessriskthatanyscenariosbypass
therules.Whenitcomestomaintainabilityandproductivity,theDatatierisnotthewaytogofor
somerules.Dependingonyourbackgroundasadeveloper,storedproceduresmightbeareal
productivitytrapifusedforbusinessrules.
Finally,whenitcomestofarmandclusterenabling,theDatatierisaslightlybetterlocationfor
businessrulesbecauseitislesscommonthatthedatabaseisscaledout.Then,thereisnoproblem
ofchangingaruleatseveralserversatexactlythesametimebecausetheruleisonlylocatedat
oneserver.Notahugedifference,butadifferenceallthesame.
What's Next
Forseveralchaptersnow,Ihavebeengoingonaboutmyproposalforhowtoaccessthedatabase
withthehelpofcreatinganSQLscriptthatissentinonego.Inthenextchapter,Iwillexplorethis
patternindepth.Iwillalsodiscussserver-sidecachingandhowtosendalotofdataineachstored
procedurecall.
References
1.R.Ross.The Business Rule Book, Second Edition.BusinessRuleSolutions;1997.
2.J.MartinandJ.Odell.Object-Oriented Methods, Second Edition.PrenticeHall;1998.
3.T.Halpin.Information Modeling and Relational Databases.MorganKaufmann;2001.
4.S.SundbladandP.Sundblad..Designing for Scalability with Microsoft Windows DNA.Microsoft
Press;2000.
Chapter 8. Data Access
IN THIS CHAPTER
N MyProposalforaDataAccessPattern
N TheDataAccessProposalintheArchitecture
N SayingMoreinFewerWordsWhenCallingStoredProcedures
N Server-SideCaching
N DealingwithSchemaChanges
N EvaluationofMyProposal
Forseveralchaptersnow,I'vebeendiscussinghowimportantitistopreparefordebugging,to
makeuseofagoodarchitecture,tofocusontransactions,andsoon.Inthischapter,we'lllookat
anotheraspectthatisextremelyimportantwhenyouwanttobuildscalable,reliable,and
maintainablesystems-dataaccess.AsScottGuthrie,thearchitectofASP.NET,says"Thinkhard
aboutdataaccess.Thisisreallythedifferencebetweensuccessfulapplicationsandunsuccessful
applications.
1
Thischapterwillfocusonanunusual,butveryeffective,patternforhowtointeractwiththeData
tier.Ihavetouchedonthispatterninearlierchapters,andIwillalsobediscussingitinthecontext
ofthearchitecturepresentedinChapter5,"Architecture.Afterthat,Iwilldiscusshowtosendan
"arrayofrowstoastoredprocedure,aswellaslookatasmallsamplingofserver-sidecaching.
Finally,we'lldiscusshowtoprepareyourcodeforfutureschemachanges.
My Proposal for a Data Access Pattern
Youhaveseveralstylestochoosefromwhenyouaredecidinghowtocallthestoredproceduresin
theDatatierfromtheBusinesstier.BeforeIgetintomyproposal,I'dliketobrieflyrecaphowthe
interactionbetweentheBusinesstierandtheDatatierisnormallyhandledusingADO.NET's
SqlCommand class.
How Stored Procedures Are Normally Called with ADO.NET
InListing8.1,youcanseeanexampleofcodethatusesADO.NETforcallingastoredprocedure
calleda_Errand_Delete().
Listing 8.1 Typical Code for Using ADO.NET to Call a Stored Procedure
Dim aConnection As New SqlClient.SqlConnection(connectionString)
Dim aCommand As New SqlClient.SqlCommand("a_Errand_Insert", aConnection)
Try
aCommand.CommandType = CommandType.StoredProcedure
aCommand.Parameters.Add(New SqlClient.SqlParameter("@RETURN_VALUE", _
System.Data.SqlDbType.Int, 4, _
System.Data.ParameterDirection.ReturnValue, False, CType(10, Byte), _
CType(0, Byte), "", System.Data.DataRowVersion.Current, Nothing))
aCommand.Parameters.Add(New SqlClient.SqlParameter("@id", _
SqlDbType.UniqueIdentifier)).Value = Guid.NewGuid()
aCommand.Parameters.Add(New SqlClient.SqlParameter("@description", _
SqlDbType.VarChar, 50)).Value = "Problem with..."
'And so on for the rest of the parameters...
aConnection.Open()
aCommand.ExecuteNonQuery()
'More about Catch blocks in the next chapter.
Finally
Jnsk.Db.Helper.ReleaseConnection(aConnection)
End Try
AsyouseeinListing8.1,it'srelativelystraightforwardtomakeacalltoastoredprocedurewith
ADO.NET.Mostdeveloperswillbeaccustomedtousingthistechnique,butI'mnottotallysatisfied
withthisapproach.IseeafewshortcomingswiththetypicalADO.NETwayofcallingstored
procedures,includingthefollowing:
N Yourcodewillbelengthybecauseyouareforcedtowritealotofcodeforeveryparameter.
N YourcodebecomestightlycoupledtoADO.NET.WhathappensifMicrosoftchangesthe
syntaxagain?Manybetatestersrecognizedthisproblembetweenbeta1and2ofADO.NET.
Thedeveloperswhohadusedcustomhelperswerehappy,buttheotherswerenot.Ina
way,theshorthistoryofandmovefromDataAccessObjects(DAO)toRemoteData
Objects(RDO)toActiveXDataObjects(ADO)toADO.NETalsoprovidessupportforthis
argument.
N Youcan'tusepureT-SQLtransactions,orrather,thereisnopointinusingpureT-SQL
transactionsinsteadofADO.NET-controlledtransactions.Youcouldhandletheprevioustwo
disadvantageswithhelpers,butthedisadvantageofnotbeingabletousepureT-SQL
transactionsdirectly requiresatotallydifferentapproach.Itmightnotbesobadtouse
ADO.NET-controlledtransactionsinstead,butyourtransactionswillbeslightlylonger.
AnothertypicalapproachdevelopersuseforaccessingthedatabasethroughADO.NETis
"registeringwhichstoredproceduretouseforchangedrowsinaDataSet,whichstoredprocedure
touseforaddedrows,andwhichstoredproceduretousefordeletedrows.Listing8.2providesa
codesampleinwhicha_Errand_Insert() isregisteredasthestoredproceduretouseforadded
rowstotheDataSet.(Inthiscase,thecodeshowniswhatyoucanautogeneratewiththehelpof
thecomponentdesigner.)
Listing 8.2 Code for Registering a Stored Procedure to Execute Once for Each Added Row
in a DataSet
Me.SqlInsertCommand1 = New System.Data.SqlClient.SqlCommand()
...
Me.SqlInsertCommand1.CommandText = "[a_Errand_Insert]"
Me.SqlInsertCommand1.CommandType = System.Data.CommandType.StoredProcedure
Me.SqlInsertCommand1.Connection = Me.SqlConnection1
Me.SqlInsertCommand1.Parameters.Add _
(New System.Data.SqlClient.SqlParameter("@RETURN_VALUE", _
System.Data.SqlDbType.Int, 4, _
System.Data.ParameterDirection.ReturnValue, False, _
CType(10, Byte), CType(0, Byte), "", _
System.Data.DataRowVersion.Current, Nothing))
Me.SqlInsertCommand1.Parameters.Add__
(New System.Data.SqlClient.SqlParameter("@id", _
System.Data.SqlDbType.UniqueIdentifier, 16))
Me.SqlInsertCommand1.Parameters.Add _
(New System.Data.SqlClient.SqlParameter("@description", _
System.Data.SqlDbType.VarChar, 50))
...
Me.SqlDataAdapter1.InsertCommand = Me.SqlInsertCommand1
Thedisadvantagesmentionedwiththe"manualtechnique(lengthycode,tightcouplingto
ADO.NET,andnopureT-SQLtransactions)alsoapplyhere.Afurtherdisadvantageisthatyoulose
somecontroloverexactlywhatisgoingon,andyouhavetolearnanothertechniqueforhandling
problems,apartfromcallingthestoredproceduresonyourown.Itmightnotbecompletelyfairto
makecomparisonswiththedreadedUpdateBatch() ofADO-thenewapproachinADO.NETis
muchbetterbecauseyouhavesomecontroloverwhatmethodswillbeusedindifferentcases.
Still,thedegreeofcontrolyouhaveisn'talwaysenough.TheriskoffuturechangesbyMicrosoft
andnodirectopportunitytousepureT-SQL transactionsapplyheretoo.Therefore,let'stakea
lookatmynewproposal,whichavoidssomeoftheseproblems.
My Data Access Proposal in Detail
IwrotethefirstversionofmydataaccesspatterninVB6foranapplicationinwhichIhadaslow
connectionbetweentheBusinesstierandtheDatatier.Sincethen,I'veuseditforapplications
withordinaryconfigurationstoo.(IfyouwanttoreadabouttheVB6version,you'llfindanarticle
called"RoundTripsAreEvil-ReduceThem!inPinnaclePublishing'sVisual Basic Developer,July
2001.
2
)Let'sdiscussmynewversionofthedataaccesspattern,rebuiltfromscratchfor.NET.
ThefirstgoalofthepatternwastoreduceroundtripsbetweentheBusinesstierandtheDatatier
bydoingmorebetweenthosetiersateachroundtrip.Therefore,Icollectallnecessaryinformation
aboutallthestoredprocedurecallsthatwillexecutetogether,andIbuildanSQLscriptthatis
executedinoneroundtrip.
ThesecondgoalwastocomeupwithanefficientapproachforhowtousepureT-SQLtransactions
whenatransactionhastospanseveralstoredprocedurecalls,whichis,ofcourse,acommon
requirement.TheSQLscriptsolutionsolvesthistoo.
Abonuswiththeproposalisthatyouhaveagreatdebuggingopportunitybecauseyoucan
intercepttheSQLscript,copyit,andexecuteitintheSQLQueryAnalyzer.ChangetheSQLscript
slightlyuntilyougettheexpectedresult,andthenmakethenecessarychangesinthecodesothat
thedesiredSQLscriptisgenerated.
Note
Normally,IhatescriptssuchasthosewritteninVBScriptforoldASP,forexample,but
autogeneratedSQLscriptsaresomethingelse!
Yetanotherbenefitofmyproposalisthatyougetahelperfordecreasingtheamountofcode:
YourcodeisdecoupledfromADO.NETtobetterhandlefuturechanges,andyourproductivityis
increased.
The Generated SQL Script
Beforewegetgoingonhowmydataaccesspatternproposalcanbeusedandhowitworks,let's
takealookattheresultwehopetoachieve.InListing8.3,youcanseeanexampleofanSQL
scriptcreatedwiththedataaccesspattern.IntheSQLscript,thepublicstoredprocedure
a_Errand_Insert() iscalled.
Note
InthecodeinListing8.3,Idecidedtoonlyshowtwoparameters.Addingmore
parametersdoesn'treallyaddanyvaluetothedescription.Thisappliestoseveralofthe
codeexamplesinthischapter.
Listing 8.3 Script Created with the Data Access Proposal
DECLARE @theSource uddtSource
, @anError INT
, @anErrorMessage uddtErrorMessage
, @aReturnValue INT
SET NOCOUNT ON
SET @anError = 0
SET @anErrorMessage = ''
SET @theSource = 'SQL script used by ErrandReport'
EXEC JnskTrace_Begin @theSource
DECLARE @theTranCountAtEntry INT
SET @theTranCountAtEntry = @@TRANCOUNT
IF @theTranCountAtEntry = 0 BEGIN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRAN
END
EXEC @aReturnValue = a_Errand_Insert
@id='A9FC3FAB-B17D-464f-9DFF-2EFF99AFEF69'
, @description='Problem with...', ...
SET @anError = @@ERROR
IF @anError <> 0 BEGIN
SET @anErrorMessage =
'@@ERROR-problem with call to a_Errand_Insert'
GOTO ExitHandler
END
ELSE IF @aReturnValue <> 0 BEGIN
SET @anError = @aReturnValue
SET @anErrorMessage =
'@aReturnValue-problem with call to a_Errand_Insert'
GOTO ExitHandler
END
ExitHandler:
IF @theTranCountAtEntry = 0 AND @@TRANCOUNT > 0 BEGIN
IF @anError = 0 BEGIN
COMMIT TRAN
END
ELSE BEGIN
ROLLBACK TRAN
END
END
IF @anError <> 0 BEGIN
EXEC JnskError_Raise @theSource
, @anError, @anErrorMessage
END
EXEC JnskTrace_End @theSource
Notethatinreality,Iwouldn'tuseanexplicittransactioninthescriptshowninListing8.3because
onlyonestoredprocedureiscalled.Iamonlyusinganexplicittransactiontoshowhowitisdone
andhowitlooksinthescript.Ifthestoredprocedurea_Errand_Insert() needsatransaction,it
willstartoneonitsown.ThedeveloperwhowritesthecodethatgeneratestheSQLscript
shouldn'thavetoknowabouttheinternalsofthestoredprocedures.Thisisespeciallyimportantif
astoredprocedureisgoingtobechangedsothatatransactionisneededeventhoughitwasn't
neededbefore.Theexceptiontothisiswhen ascripthastodecidewhatTransactionIsolation
Level(TIL)touse.Then,thedevelopermustknowagreatdealabouttheinternalsofthestored
procedures.Onceagain,ifnoexplicittransactionhastobestartedfromthescript,butthestored
procedureneedsatransaction,thestoredprocedure(orratherthedeveloperofthestored
procedure)willdecidewhatTILtouse.
Note
AsyoucanseeinListing8.3,therearemanysimilaritiesbetweenthegeneratedSQL
scriptandthestandardizedcodestructure forstoredproceduresthatwasdiscussedin
Chapter5.TheSQLscriptalsoinvolvesagreatdealoferrorhandling,whichIwilldiscuss
indetailinChapter9,"ErrorHandlingandConcurrencyControl.
TheSQLscriptshowninListing8.3willthenbesenttothedatabaseandexecutedwithoutany
extraroundtripsbetweentheBusinesstierandtheDatatier.Thisiswhatweareafter.Note
especiallyhowthetransactionishandled.It'sexactlythesameapproachastheoneIdiscussedfor
T-SQLtransactionsinstored proceduresinChapter6,"Transactions.Ifthereisalreadyanactive
transactionwhentheSQLscriptstartstoexecute(forexample,aCOM+transaction),Idon't
handletransactionsintheSQLscript.
External Design of My Data Access Proposal
WhenIreferto"externaldesign,Imeanthepartofthedesignthattheconsumerofthepattern
willseeandinteractwith.Tospeakplainly,thesearetheclassesandtheirpublicmembers.
Asfarastheconsumerofthedataaccessproposalknows,heorsheissupposedtocreatea
Jnsk.Db.BatchCommand instanceandcallAddSprocCall() oneormoretimes.TheBatchCommand
willrememberwhatcallstostoredproceduresshouldbedone.Finally,theBatchCommand instance
shouldbesenttotheAcme.HelpDesk.PersistentAccess.paHelper classforexecutingallthe
storedprocedurecalls.Youcanseetheclassdiagramfortheclassesthatareatthecenterofmy
dataaccessproposalinFigure8.1.
Figure 8.1. Class diagram for my data access proposal.
AsyoucanseeinFigure8.1,paHelper inheritsfromBatchCommandHelper.TheConnect() method
isProtected andwilloftenbeoverriddeninpaHelper,buttherestofthemethodsdon'ttypically
havetobeoverridden.
It'salsoworthmentioningthattheBeginTransaction() methodofBatchCommand requiresthe
TIL.DecidingonwhatTILtousecanbedifficult.It'sseldomthatSERIALIZABLE isneeded,but
somedevelopersuseSERIALIZABLE justincase.IprefertousetheTILthatisthelowestbutstill
sufficientand correctforthesituation.
Note
TimEwald'sTransactional COM+: Building Scalable Applications
3
givesyouseveral
concretetipsonhowtochoosethecorrectTIL.
Figure8.1showsthestaticviewofthedesign.InFigure8.2,youcanseetheclassesinaction.I
haveaddedafewclasses-ErrandReport fromtheApplicationlayeranddoErrand fromthe
Domainlayer-inthisfiguretogiveacompleteexample.Iwilldiscussthoseclassesinmoredetail
laterinthechapter.
Figure 8.2. Interaction diagram for my data access proposal.
Note
Itiscommonlyregardedasbaddesignifpublicmethodsmustbecalledincertain
sequencestogivethecorrectresult.AsyounoticefromFigure8.2,thereisastrict
sequencetofollowtogettheexpectedresult.Forexample,youhavetocall
BeginTransaction() beforeyoucallAddSprocCall() forthestoredprocedurecallsthat
youwanttohaveinthetransaction.Thisisnaturaltomostdevelopers,soitshouldn't
causeanyproblems.Forexample,asimilarprotocolalsoappliesfortransactionsinADO.
AsyoucanseeinFigure8.2,thedesignusesaninstanceofBatchCommand asastream,orbag,of
whatistobedone.Theinstanceissentaroundbetweenthelayersandfinallyusedbythe
paHelper classinthePersistentAccesslayertoexecuteallthestoredproceduresinoneroundtri p
tothedatabase.YoucanalsoseeinFigure8.2thatitistheApplicationlayerclassErrandReport
thatdecideswhenthetransactionwillstartandendandthatcontrolsthecompletescenario.
Note
MyproposalisfairlysimilartotheCommandProcessorpatternthatisdiscussedinFrank
Buschmann,RegineMeunier,HansRohnert,PeterSommerlad,andMichaelStal'sPattern-
Oriented Software Architecture: A System of Patterns.
4
TheCommandProcessorpattern
initsturnisrelatedtotheCommandpatternthatisdiscussedinDesign Patterns:
Elements of Reusable Object-Oriented Software byErichGamma,RichardHelm,Ralph
Johnson,andJohnVlissides.
5
Sample Code Using the External Design
Enoughisenough,let'sgetdowntobusinessandlookatsomecodethatusestheexternaldesign.
Code that Generates an SQL Script and Sends It for Execution
First,theSQLscripthastobegenerated.YoucanseehowthatisdoneinListing8.4,wherethe
BatchCommand instanceisfirstinstantiatedandatransactionisstarted.(Onceagain,anexplicit
transactionisn'tneededinthisspecificexample.Ihaveuseditfordemonstrationpurposesonly.)
Next,theBatchCommand instanceissenttodoErrand togetastoredprocedurecalladded
(doErrand willdelegatethetasktopaErrand,butthatistransparenttothecodeinListing8.4).
Listing 8.4 ErrandReport.Report()
Public Sub Report(ByVal dataSet As DataSet)
Const theSource As String = theTypeName & ".Report"
Dim aBatchCommand As New Jnsk.Db.BatchCommand(theSource)
Try
aBatchCommand.BeginTransaction(IsolationLevel.ReadUncommitted)
Acme.HelpDesk.Domain.doErrand.Save(dataSet, aBatchCommand)
Acme.HelpDesk.PersistentAccess.paHelper.Execute(aBatchCommand)
'Some Catch blocks...
End Try
End Sub
Note
TheconstructoroftheBatchCommand classtakesfromSource asaparameter.Thisisused
(asyoucanseeinListing8.3)whenbuildingtheSQLscriptandwhenaddingtracecalls,
forexample.
Finally,whentheErrandReport instancehascollectedallthedataintheBatchCommand instance,it
sendsittopaHelper forexecutionagainstthedatabase.Asyousee,Ididn'tcall
EndTransaction().Youdon'thavetodothis.IfyouhavecalledBeginTransaction(),thescript
willendthetransactionanyway.ThereasonforhavingtheEndTransaction() methodisifyou
wanttoendthetransactionbeforethescriptends,whichishighlyrecommendedifyouaregoing
tocallmorestoredproceduresattheendofthescriptthatdon'tneedtobeinthetransaction.
Am I Promoting a Weak Protocol?
ItmightseemthatIampromotingaweakprotocolinsayingthatthe
programmerdoesn'thavetoendthetransactionexplicitlybecausethe
scriptwillendthetransactionautomatically.Ofcourse,Idon'twantto
encouragesloppyprogramming.Thereasonwhyyoudon'thavetocall
EndTransaction() explicitlyinthisdesignisthattheerror-handling
techniqueIuseforSQLscriptsandstoredproceduresrequiresan
ExitHandler sectionthatwillhandleopentransactions,ifappropriate.
YouwillfindmoreinformationaboutthisinChapter9.
Note
IfyoudecidetodoworkbeforeandafterthetransactionstartsintheSQLscript,youget
differentsemantics,dependingonwhetherCOM+transactionsareusedornot.When
COM+transactionsareused,thepre-andpost-workwillbedoneinsideatransaction,
whichisaveryimportantpointtoconsider.
InListing8.5,youcanseehowthepaErrand addsacalltothestoredprocedure
a_Errand_Insert().(WecameherefromdoErrand.Save().)Asyoucansee,manyconstantsare
required,notonlyforthenamesofthestoredprocedures,butalsoforthenamesofthe
parametersinthestoredprocedures.(Theconstantsarecalled,forexample,
Sprocs.DataTables.errand andSprocs.Names.a_errand_Insert.)
Listing 8.5 paErrand.Save()
Public Shared Sub Save(ByVal dataSet As DataSet, _
ByRef batchCommand As Jnsk.Db.BatchCommand)
Dim aDataRow As DataRow
Dim aDataTable As DataTable = _
dataSet.Tables(Sprocs.DataTables.errand).GetChanges(DataRowState.Added)
For Each aDataRow In aDataTable.Rows
batchCommand.AddSprocCall(Sprocs.Names.a_Errand_Insert)
batchCommand.AddParameter(Sprocs.Parameters.id, _
CType(aDataRow(Sprocs.Parameters.id), Guid), False)
batchCommand.AddParameter(Sprocs.Parameters.description, _
CType(aDataRow(Sprocs.Parameters.description), _
String), Sprocs.ParameterSizes.description, False)
'And so on for all parameters...
Next
End Sub
Note
Atfirst,IusedaSprocCall classthatwasinstantiatedonceforeach calltoastored
procedureandthentheinstanceswereaddedtotheBatchCommand instance.Ifindthe
currentdesignwithouttheSprocCall classtobeslightlymoreencapsulated.Ifa
SprocCall isusedinternallyintheBatchCommand ornot,thatisinvisibletotheconsumer
oftheBatchCommand.
Code That Executes the SQL Script
It'snowtimetousetheSQLscriptandsendittothedatabase.Therearethreedifferentmethods
fordoingthis,dependingonwhethertherewillbearesultsetfromtheSQLscriptornotandhow
thatresultsetshouldbeused.InListing8.6,IshowyouthefirstpartoftheExecute() method,
whichisusedwhennoresultsetisexpected.
Listing 8.6 BatchCommandHelper.Execute(): Part 1
Public Shared Sub Execute(ByVal batchCommand As BatchCommand)
Dim anSqlScript As String = batchCommand.GetSqlScript()
AsyoucanseeinListing8.6,theBatchCommand instance(sentasaparameter)totheExecute()
methodisaskedforthetotalSQLscriptwiththehelpoftheGetSqlScript() method.
Thisisthereasonfortheexistenceoftheget only propertyNoOfSprocCalls.Ifyoudiscoverthat
itismoreefficienttouseanSqlCommand whenthereisonlyoneortwostoredprocedurecallsto
make,youcancheckNoOfSprocCalls anddeterminewhetheryoushoulduseafuturesiblingof
GetSqlScript() called,forexample,GetSqlCommands(),thatreturnsanarrayofSqlCommand
objects.
Note
Inaway,youcanseethesolutionwiththeproposedhelperasasimpleoptimizer.
InsteadofoptimizingSQLstatements astheoptimizerinSQLServer,thisoptimizercan
investigateallthestoredprocedurecallsandexecutetheminanappropriateway.You
can,ofcourse,alsoextendthehelpertomakeitconfigurable,sothatyoucanprovide
informationina.config file.
ThecodeinListing8.7isfromtheBatchCommandHelper class,butthecalltoConnect() will
actuallygotothepaHelper class,whichoverridesConnect() inBatchCommandHelper.
Note
Theconnectionstringisdealtwithina.config fileandcachedinthe waythatwas
discussedinChapter4,"AddingDebuggingSupport.
Listing 8.7 BatchCommandHelper.Execute(): Part 2
Dim aConnection As SqlClient.SqlConnection
Try
aConnection = Connect()
Finally,it'stimetosendthescripttothedatabase.Doingthisisstraightforward,asyoucanseein
Listing8.8.
Listing 8.8 BatchCommandHelper.Execute(): Part 3
Dim aCommand As New SqlClient.SqlCommand(anSqlScript, aConnection)
aCommand.CommandType = CommandType.Text
aCommand.ExecuteNonQuery()
'And some catch blocks...
Finally
Jnsk.Db.Helper.ReleaseConnection(aConnection)
End Try
End Sub
Internal Design of My Data Access Proposal
Ihavetriedtomaketheexternaldesignasfreeaspossiblefromtheimplementationitselfand
fromthefactthatintheend,anSQLscriptwillexecuteagainstthedatabase.Forexample,the
AddParameter() methodshaveabunchofoverloadedversions,oneforeachsupporteddatatype,
eventhoughtheSQLscriptwill,ofcourse,beinplaintext.Bynotexposingimplementationdetails
intheexternaldesign,youcanchangetheimplementationanduseaplainADO.NET
implementationinstead,forexample,internally.Youmightwonderwhatyouhavegainedbydoing
this.Well,youhaveahelperthathelpsyouwritelesscode,andyoudecouplealotofyourcode
fromADO.NETsyntax.Idefinitelyrecommendthatyouuseahelpertobecomemoreproductive,
regardlessofwhetheryoulikemyproposal.
Asyouknow,IlikemySQLscriptsolution,sothatistheimplementationIuseinternallyrightnow,
butIhavealsopreparedthedesignforotherfutureimplementations.I'dnowliketotellyouabout
afewofthekeydecisionsoftheinternaldesign.
Storage
Thereareseveralpossiblesolutionstochoosefromwhenitcomestorememberingwhatstored
procedurecallstheSQLscriptshouldcontain,whenthetransactionshouldbestartedandended,
andsoon.IcanusesomesortofcollectionandwhenGetSqlScript() iscalled,therequested
typeofoutputiscreated.IcurrentlyonlysupportSQLscriptsasaresultstreamandsoIstorethe
SQLscriptasaStringBuilder thatisbuiltduringthegenerationoftheSQLscript.Finally,when
GetSqlScript() iscalled,thecompletescriptisdone,exceptfortheendsectionthatisadded,
andthentheSQLscriptisreturnedasaString.
UsingaStringBuilder inasituationliketheonejustdiscussedisfast,butnotasmemorysaving
asmightfirstbeexpected.EachAddSprocCall() willaddmanybytestotheStringBuilder,
mainlybecauseoferrorhandling.Thisappliestoeachstoredprocedurecall.Meanwhile,because
thestoragedesignistotallyinternalto BatchCommand,youcaneasilyalterthedecisionwithout
affectingtheBatchCommand consumer.OnlytheBatchCommandHelper willbeaffected.
Note
WhenBatchCommand supportsdifferentoutputmethodsinthefuture,itmightbepossible
toaddaconstructorto BatchCommand thattakesaparameteranddescribeswhatstorage
typetouse,namelythesameonethatwillbeusedwhentheoutputmethodiscalled.
Thatcouldhelpmakeiteasiertooptimizetheconstructionslightly.
Named Parameters
WhenAddSprocCall() isused,itwillfinallyleadtoacallforastoredprocedureintheSQLscript.
EachcalltoAddParameter() willaddaparametertothestoredprocedurecall.Soasnottocreate
yetanotherorderdependencyfortheorderinwhichtomakethecallstoAddParameter(),I
decidedtousenamedparametersinstead.TheresultintheSQLscriptcanbeseeninListing8.9.
Listing 8.9 Example Showing That Named Parameters Are Used for Calling Stored
Procedures
EXEC @aReturnValue = a_Errand_Insert
@id='A9FC3FAB-B17D-464f-9DFF-2EFF99AFEF69'
, @description='Problem with...', ...
Anotheradvantageofusingnamedparameters,apartfromthefactthattheparameterorder
doesn'tmatter,isthatyoucanchoosetouseonlycertainparameters.It'salsoabighelpwhenyou
debugmalfunctioningSQLscripts.
However,asusual,therearesomedisadvantagestousingnamedparameterstoo.One
disadvantageisthatifthenameofaparameterchanges,yourcodebreaks.Inaddition,itwillbe
moretedioustowriteastoredprocedurecall.(Thisisn'taproblemwhenAddParameter() ora
similarhelperisdoingthejobforyou,ofcourse.)
Encoding Dangerous Parameters
Whenyouusethebuilt-insolutioninADO.NET(andsimilartechniques)foraddingparametersto
storedprocedurecalls,youwillautomaticallygethelpwithdangeroussigns,suchasextraquotes
instrings.Whenyoubuildasolutiononyourown,youarenotgiventhatluxuryautomatically,but
AddParameter() willtakecareoftheproblem.Theencodingwillbedealtwithaccordingtowhich
AddParameter() methodisused.Forexample,theBoolean versionwilltranslateTrue to1inthe
SQLscriptandFalse to0.TheDate versionwilltranslatedatestodatesformattedinaspecific
wayintheSQLscript.AlltheDecimal versionswilloutputdotasthedecimalseparatorand,of
course,theString versionofAddParameter() willhandlethequoteproblem.
Beware of the ISO Date Format!
WhenyouareformattingdatestowritetotheSQLscript,youmustbe
carefulifyouwantyourcodetobeinsensitivetoregionaldatesettings.
MyfriendTiborKaraszi(SQLServerMicrosoftMVP)warnsthatwemust
watchoutfortheISOformat.ThecodeshowninListing8.10illustrates
thedangeroftheISOdateformat.
Listing 8.10 The Danger Inherent in the ISO Date Format
SET language us_english
SELECT CAST('1998-03-25' AS datetime)
SET LANGUAGE french
SELECT CAST('1998-03-25' AS datetime)
Trysp_helplanguage andyouwillseethatlanguagecarriesthe"ymd
format.Inotherwords,Karaszisaysthatifyou'reabouttouseANSISQL
dateformatting,seetoitthatyourconnectionhasSET DATEFORMAT ymd
set.Alternatively,youcanformatthedateasshowninListing8.11.
Listing 8.11 An Alternate Date Format
SET language us_english
SELECT CAST('19980325' AS datetime)
SET LANGUAGE french
SELECT CAST('19980325' AS datetime)
YYMMDDis"safe.ItisokaytodroptheseparatorsaccordingtoISO
8601,butnotaccordingtoANSISQL.AsKaraszinotes,thisisstrange
becauseANSISQLissaidtobuildonISO8601regardingrepresentation
ofdatesandtimes.
Debugging
AsImentionedearlierinthechapter,thedebuggingexperiencewiththedataaccesspatternis
verygood.YoucansetabreakpointinBatchCommandHelper.Execute(),copythecompletescript,
anduseitinSQLServerQueryAnalyzeroverandoveragainuntilyouhavefoundtheproblem.
Note
WhetherthescriptcanbeexecutedrepeatedlyinSQLServerQueryAnalyzerwiththe
sameresultdependsonwhatthescriptisdoing.Itisn'talwayspossible.
Noteespeciallyhow@anErrorMessage isusedinListing8.3.Itholdscontextinformationsothatif
theSQLscripthasseveralstoredprocedurecalls,youwillseeexactlywhichcallhadtheproblemin
theerrormessage.Ifthesamestoredprocedureiscalledseveraltimes,the@anErrorMessage
variablecanalsocontaininformationaboutwhichofthecallsitwas.
Anotherbenefitofthecentralizationofallthestoredprocedurecallstoahelperisthatyoucan
easilyaddtracecallsthatshowalltheparametervaluesusedwhencallingthestoredprocedures.
TheAddParameter() (orAddSprocCall())canbechanged,soithelpsgeneratescriptcodefor
tracing,asshowninListing8.12.
Listing 8.12 Extract from SQL Script Where Tracing Code Has Been Added Automatically
for Tracing All Parameter Values
SET @aTraceMessage =
'@id=A9FC3FAB-B17D-464f-9DFF-2EFF99AFEF69
, @description=Problem with..., ...'
EXEC JnskTrace_Message @theSource, @aTraceMessage
EXEC @aReturnValue = a_Errand_Insert
@id='A9FC3FAB-B17D-464f-9DFF-2EFF99AFEF69'
, @description='Problem with...', ...
YoucancallGetSqlScript() toseewhattheSQLscriptcurrentlylookslikeatanytimeduringthe
buildprocess,whichcanalsobegoodfordebuggingpurposes.Justonewordofcaution:The
GetSqlScript() methodwilladdanendsectiontotheSQLscript,butthissectionwill"disappear
ifyoucontinuetocallAddSprocCall() (thatis,GetSqlScript() addsthatsection).
Connection Strategy
Acommondiscussioninnewsgroupsishowtheconnectiontothedatabaseinn-tierapplications
shouldbehandled.Somedevelopershavedecidedtogoallthewaywiththe"fastin,fastout
methodinwhichtheyopenaconnectionandcloseitineverymethod,eveniftheyneedthe
connectionagaininanothermethodduring thesamescenario(byscenario,Imeanamethodcall
totheApplicationlayer).Becauseofconnectionpooling,thisworksquitewell,butevenso,
connectionpoolingtakesresourcestomanipulate,andtherewillbeashortdelaywhenfetchinga
connection fromtheconnectionpool,ofcourse.
Ithinktheruleofthumbshouldbetoopentheconnectionaslateaspossibleduringascenarioand
thenkeepthatconnectionopenthroughoutthecompletescenariotobeusedbyallmethods.As
youknowbynowfrommy dataaccesshelper,itwilltrytomakeonlyonecalltothedatabase
duringascenario,butthisruleofthumbalsoappliesforscenarioswhereseveralcallsaremadeto
thedatabase.However,asalways,thereareexceptionstotherule,sodon'tuseitblindly.Be
especiallycarefulthatyoudon'tsendaconnectionoveraprocessboundary.
OUTPUT Parameters
IfyouwanttocatchtheOUTPUT parameterfromonestoredprocedureanduseitinanotherstored
procedure,youcanusetheAddCustomDeclaration() methodoftheBatchCommand todeclarethe
variable.ForthestoredprocedurecallwiththeOUTPUT parameter,youuseAddParameter() as
usual,buttheoutput parametermustbeTrue,andthevalue parametershouldbethenameof
thevariable-forexample,@id.Forthestoredprocedurethatisthentousethevariable,youjust
usethenameofthevariableagainasthevalue parameterwhenyoucallAddParameter().
Note
ThispartoftheproposalshowsinternalinformationabouttheSQLscriptsolution.If
you'reabouttoaddsupportforGetSqlCommands(),forexample,youneedtotakea
closerlookatthis.
Tricky Cases of SQL
Twooftheproblemswithusingonlystoredproceduresforinteractingwiththedatabasearewhen
youhavetouseanIN clauseinyourSELECT statementorwhentheusershouldbeabletodecide
whichcolumntousefortheORDER BY clause.Inbothcases,Istillusestoredprocedures.I
typicallypacktheIN clauseasaVARCHAR thatIsendtothestoredprocedure.Thestored
procedurewillthenhavetousedynamicSQL.OneoftheproblemswithdynamicSQListhatthe
usermusthavegrantedrightstousetheobjectthatisusedbydynamicSQL.It'snotenoughto
havegrantedrightstothestoredprocedurethatexecutesthedynamicSQL.Totakecareofthis
problemasmuchaspossible,Ialwaysuseviewsoruser-definedfunctions(UDFs)fromthe
dynamicSQLandseetoitthatit'sobviousfromthenameofthevieworUDFthattheusermust
havegrantedrightstoit.
Note
WhenIsaytheusermusthavegrantedrights,theuseristheonewhoactsasthe
principalforexecutingtheCOM+application,nottheenduser.Thatis,youshouldusea
dedicatedaccount(thatdoesn'tbelongtoanenduser)forexecutingtheCOM+
application.
InthecaseoftheORDER BY clause,thestoredprocedurewilltakeaparameterdescribingthe
ORDER BY clausethatisrequested.IoftenstaywithstaticSQL,soIhavetorepeatthesame
SELECT statementonceforeachpossibleORDER BY clause.Whentherearetoomanypossible
ORDER BY clausesforthisapproach,IusedynamicSQL,asIdidwiththeIN clause.
Disadvantages with the Proposal
Asusual,Iwillevaluatethedataaccessproposalattheendofthechapteragainstthecriteriawe
discussedinChapter2,"FactorstoConsiderinChoosingaSolutiontoaProblem.However,I'dlike
todiscusssomepossibledisadvantagesherefirst,becausesofarIhaveonlytoldyouhowterrific
theproposalis.
OnelikelyproblemisthattheproposalistargetedatdeveloperswhoalsofindT-SQLworth
mastering.IfyouthinkthatyouareonlygoingtobeaVBprogrammer,thedataaccesssolutionis
probablyhardtouse anddebug.Inaddition,thedesignissimilartoHandlesintheWin32API.
Becauseofthis,itdoesn'tfeelsoup-to-date.NotthatIthinktrendsshouldgoverndesign
decisions,ofcourse.Usewhatevergetsthejobdonewell.Whileweareatit,itcouldbeabadidea
totossobjectsaroundbetweenlayers,becauselayerscanliveindifferentprocesses.Ontheother
hand,youshouldhaveoptimizedforfewroundtripsbetweentiersbecausethatistheplacewhere
therewillbeprocess-to-processcommunicationandnotbetweenlayerswithinatier.
Inaddition,thegeneratedSQLscriptoftenbecomesquitelargebecauseoftheerrorhandling,and
moredatawillbesenttotheDatatiercomparedtowhenusualADO.NETcodeisused.
Furthermore,theSQLscriptcodeisdynamic,soithastobecompiledeachtime.Also,inVisual
Studio.NET,therearebuilt-inRapidApplicationDevelopment(RAD)toolsforquicklywritingthe
codetocallastoredprocedure.Withmydataaccessproposal,youcan'tusethosetools,butit's
quiteeasytowriteafewofyourowntoolstotacklethemosttime-consumingandrepetitivework.
Anadditionaldisadvantagewiththeproposalisthatallparametersmustbeconvertedtotext.This
costsCPUcycles,ofcourse.
Anotherproblemisthatit'shardtogetresultsbackfromtheSQLscript,exceptviaSELECT toa
DataReader (oraDataSet).
Finally,althoughit'snotreallyaproblemwiththeproposal,asyoucanseefromtheinterfaceof
BatchCommand,Ihavedecidednottohavemethodsforlogicotherthanjustcallingstored
procedures.ItwouldbepossibletouseIF clausesandsuchinthescripttoo,butIchosenottodo
thisbecausethehelpersolutionwouldthenbeanSQLscript-onlysolution.
Other Approaches to Data Access Helpers
Recently,IhavelearnedthatMicrosofthasbeenworkinghardonasuite
oftoolscalledDevelopmentAcceleratorsfor.NET.Developerswillbeable
usethissuiteoftoolsashelpersfortheefficienthandlingofdifferent
tasks.Oneoftheacceleratorsin thesuiteiscalledtheDataAccess
Accelerator
6
andisdefinitelyworthalook.
AsIsaidearlier,youcanalsousethebuilt-inwizardsinVisualStudio
.NETandlet themwritealotofthetediouscodeforyou,butthenyou
becomemuchtoodependentonADO.NET,withallthedrawbacksthat
entails.Moreover,whileitmaybefasttowritethecode,ittakestimeto
maintainthecode.
The Data Access Proposal in the Architecture
InChapter5,Iestablishedmyfavoritearchitecture,andinthissectionI'dliketoputthedata
accessproposalinthecontextofthatarchitecture.
InFigure8.2,yousawacompletecallstackforascenarioinwhichanApplicationlayerclass
(ErrandReport)controlstheprocessofcallingthestoredprocedurea_Errand_Insert().Asyou
saw,theproposalaffectsallthreelayersintheBusinesstier.Inaway,thisisnotstrictlylayering
butoftensomeclassesare"globaltoseverallayersinlayeredarchitectures.Inthedataaccess
proposal,classesinseverallayersareawareofandusetheBatchCommand class.
AnotherexampleofaclassthatseverallayersoftenareawareofanduseistheADO.NETDataSet.
BecauseIoftenuseDataSetsforcarryingdata,DataSetswilloftenbesenttotheconsumertoo.
What to Use for Carrying Data
IhavejustsaidthatIoftenuseDataSetsforcarryingdatabetweenlayers.BeforeItakethisidea
further,I'dliketomakeitclearthatIdon'tuseDataSetsassoonasthereisdatatoreturnfroma
storedprocedure.Itdependsonhowfarthedatawillbetransportedandwhetheraconsi stent
interfacetothePersistentAccesslayeriswanted.
InChapter5 (andillustratedinFigure5.10),IexplainedthatIliketohaveaconsistentwayof
workingwiththeclassesinthePersistentAccesslayer.Therefore,Ineedawaytosendandreturn
datawithouthavingtocontinuallychangetheinterface.Thereareseveralalternativestochoose
from,onebeingDataSetsthatcomesfreewithADO.NETandhasseveralgoodfeatures.Oneofmy
favoritefeaturesisthatyoucanputseveralDataTablesinoneDataSet.(InVB6,Iusedseveral
different"hackapproachesforthis,thebestonebeingtousearraysofRecordsets.)Othergreat
featuresincludetheopportunitytocreatestronglytypedDataSetsandtohaveconstraintsinthe
DataSet sothatsomeerroneousdatacanbecaughtbeforegoingintotheDataSet atall.Actually,
IsometimesevenuseaDataSet forreturningonerow.TheDataSet isgoodinthissituation
becauseithasalotofmetadata,butthedrawbackislargeoverhead.
Youcan,ofcourse,buildamechanismsimilartoDataSetsyourself.Onereasonfordoingthis
mightbetoaddashieldagainstfutureMicrosoftchangesinDataSets.Fornow,Ihavedecidedto
useDataSetsformytransportationneeds.
Butwhatifthedatawon'tleavethePersistentAccesslayer?Inthissituation,usingDataSetsis
overkill.Iftheresultisonlyonerow,usingOUTPUT parametersofthestoredprocedureisthemost
efficientapproach.Ifthereareseveralrowsintheresult,youshoulduseaDataReader insteadofa
DataSet.
Note
BeverycarefulwhensendingaroundDataReaders.Aconnectionmustbeopentothe
database,andyoushouldbesuretocloseconnectionsasfastaspossibleforscalability
reasons.Thatis,ifonerequestdoesn'tneedaconnectionfor,say10seconds,ifthe
connectionisclosedsoitcangobacktotheconnectionpool,theconnectioncanhelpto
servehundredsofotherrequests.
Onelastcomment:YoumaywonderwhethertheSQLscriptorthestoredprocedureshoulddecide
whetherseveralresultsetsaretobefetched.InChapter5,Iletthestoredproceduredecide,butin
realityIprefertolettheSQLscriptdothisinstead,therebycreatingmoreprimitivestored
proceduresthatcanbeusedforotherpurposes.InChapter5,returningseveralresultsetswas
mostlyusedfordemonstrationpurposes.
Saying More in Fewer Words When Calling Stored Procedures
Comparedtoanordinaryprogramminglanguage,T-SQLandstoredprocedureshaveseveral
problems.Oneoftheworstproblemsisthatarrayscan'tbeusedasparameterstostored
procedures.Inaway,thedataaccesspatternthatIhaveproposedinthischaptermakesthisless
ofaproblembecausewhenthepatternisused,thenumberofroundtripsbetweentheBusiness
tierandtheDatatierwillbereducedtoa minimum.Still,insomesituations,sendingseveralrows
inonestoredprocedurecallcanbeveryuseful.Let'stakealookatafewtechniquesyoucanuse.
XML
SQLServer2000comeswithsomenativeXMLsupport.Onepossiblewaytobenefitfromthebuilt-
insupportforXMLinSQLServer2000istousestoredprocedurestoparseXMLdocuments.
Thankstothissupport,youcansendseveralrowstoinsertinanXMLdocumentinaparameterto
astoredprocedure.
AssumeyouhaveanXMLdocumentwithacoupleof errandsthathavetobereported.The
documentisthensenttoastoredprocedurethatusessp_xml_preparedocument() toparsethe
documentandtoinstantiateanobjectmodel.Listing8.13showshowthismaylook.
Listing 8.13 Parsing an XML Document in T-SQL
EXEC sp_xml_preparedocument @aDocument OUTPUT, @xml
Whenthisisdone,youcanuseOPENXML() tofindinformationintheXMLdocument.InListing
8.14,youcanseeanexampleinwhichIuseanXMLdocumentasthesourceforanINSERT.
Listing 8.14 Using an XML Document as the Source for an INSERT
INSERT INTO problemsolver (id, firstname, lastname)
SELECT * FROM OPENXML(@aDocument,
'/root/problemsolver')
WITH (id UNIQUE IDENTIFIER '@id',
firstname VARCHAR(50) '@firstname',
lastname VARCHAR(50) '@lastname')
Finally,it'simportanttoremovethedocument,sowefreeresources,asshowninListing8.15.
Listing 8.15 Removing the XML Document from Memory
EXEC sp_xml_removedocument @aDocument
Other Formats
BeforeXMLwasevencreated,IusedcustomformatstoachievethesameeffectIshowedinthe
previoussection.Forexample,Ipackedstringswithordernumbersseparatedbycommas.Ihave
alsosimulatedanarraybyseparatingcolumnswithtabandrowswithlinefeed.(Thistechnique
wasn'treallyusefuluntilSQLServer7wasreleasedbecausethemaximumsizethenforVARCHAR
waschangedfrom255to8000.)Atthereceiverside,thestructurehastobeunpacked,whichcan
easilybedonewithastoredprocedureoraUDF.Asamatteroffact,IhavewrittenaneXtended
storedProcedure(XP)thattakesatwo-dimensionalarrayandoutputsitasaresultsetthatcanbe
caughtandputinatemptablefor,say,furtherprocessing.However,inSQLServer2000,aUDFis
abettersolution.Inanycase,XMLismostoftenthepreferredformattouse.
Bitmaps
Bysendingabitmapasaparameter,youmakeitpossibletosendseveralvaluesinone
parameter.Inaway,itwillbreaktypesafetybecauseyoucanaddanothervaluewithouthavingto
addanotherparameter.Althoughyou can'tusethistechniqueallthetimeanditissomewhat
awkwardtouse,whenyouneedanoptimizationtechnique,itcanoftenbeveryefficient.
Youcanalsousebitmapsinyourdatabasedesign.Assumethatproblemsolversthatworkwiththe
AcmeHelpDesksampleapplication(andarealsorepresentedasrowsinthedatabase)aremarked
withoneormoreofthefollowingdescriptions:
N Smart
N Committed
N Lazy
N Fast
Youcanstorethesedescriptionsinthedatabaseasusualjustbyhavingatablerelatedtoa
problemsolver table.YoucanalsoskipthisextratableandjusthaveanINT columninthe
problemsolver table.Let1meanSmart,2meanCommitted,4meanLazy,andsoon.Ifyoustore
3inthecolumn,itmeansthattheproblemsolverisSmartandCommitted.
Note
Storingbitmapsinthiswaybreaksthefirstnormalformoftherelationalmodelbecause
severalvaluesarestoredinasinglecolumnandthatisexactlywhatthefirstnormalform
forbids.Therefore,usethistipcarefully.
Let'sdiscussthisalittle further.Ifyouareabouttoregisteranewproblemsolver,youonlyhave
towriteonerowifyouuseabitmap,evenifthespecificproblemsolverhasthreedifferent
descriptions.Withtheordinarysolution,youwouldhavetowritefourrows,onetothe
problemsolver tableandthreetotheproblemsolverdescription table.
WhenitistimetosearchonlyfortheproblemsolversthatareSmartandLazy,becauseyouwant
totrytostimulatethemtobecomeSmartandCommitted,youcanpackabitmapliketheone
showninListing8.16.
Listing 8.16 Packing a Bitmap in an INT Variable with the Use of "or"
SET @aBitmapOfDescriptions = 1 | 4
Youthensendthebitmaptoastoredprocedure,andthestoredprocedureusesthebitmap
parameterinaSELECT statementliketheoneshowninListing8.17.Thistime,youuse"and
insteadof"or.
Listing 8.17 Unpacking a Bitmap with the Use of "and"
SELECT id, firstname, lastname
FROM problemsolver
WHERE @bitmapOfDescriptions & description <> 0
Finally,notethatyoudon'thavetostaywiththedatatypeINTEGER.Youcoulduse,forexample,a
BIGINT tohavetwiceasmanydifferentvalues,andyoucanalsohavealargenumberof
parameters.Anothertipistouse-forexample,POWER(2, 3)-forsettingandgettingvaluesinthe
bitmapformakingthecodeeasiertoread.
Data Transportation Between Stored Procedures
Whenonestoredprocedureneedstocallanotherstoredprocedure,thesametechniquesthatwere
justdiscussedcan,ofcourse,beusedagain,buttherearealsootheralternatives,aswe'llnow
see.
Temp Tables and Cursors
Perhapsthemostcommonsolutiontotheproblem(well,themostcommonafternotwriting
modularcode,thatis)istocreateatemptableandsenditsnametobeusedwithEXECUTE (string)
inthecalledprocedure,ortojustletthecalledprocedurehavethenamehard-coded.
Anothersolutionistocreateacursorand,forexample,returnthecursortothecaller.Thecaller
canthencontinuetousethatcursorwithoutknowingwhattheWHERE clauseforthecursorwas.In
Listing8.18,acursoriscreatedandreturnedtothecaller.
Listing 8.18 Creating a Cursor and Returning It to the Caller
CREATE PROCEDURE Sample_CursorReturn
(@cur CURSOR VARYING OUTPUT) AS
DECLARE aCursor CURSOR LOCAL FOR
SELECT id FROM problemsolver
OPEN aCursor
SET @cur = aCursor
DEALLOCATE aCursor
Bothtemptablesandcursorscreatetightcouplingbetweenstoredprocedures,whichisnotagood
thing.And,asyouknow,youshouldthinktwice(atleast)beforeusingcursorsatall.
UDFs
Often,asecondarystoredprocedurethatcreatesaresultsetforusebyaroot-storedprocedurecan
berewrittenasanInlineTableValuedFunction(ITVF)orMultistatementTableValuedFunction
(MTVF)instead.Ifyourewritethissecondarystoredprocedure,youdon'thavetheproblemof
catchingtherowsandputtingthemsomewherebeforeusingthembecausetheUDFcanbeusedin
SELECT statements,forexample,justasatableoraview.
Server-Side Caching
Itdoesn'tmatterhowfastsomethingis,notdoingitatallisfasterandalsoconservesresources.
Cachingisonewayofachievingthisabilityto"donothing.Insteadofgoingtothedatabaseserver
tofetchthelistofproblemsolvers,youcancachethelistinmemoryintheapplicationserver,and
thenyoudon'thavetodoanything.Theoperationismuchfasterandyoualsosaveresourcesat
thedatabaseserver.Thissoundstoogoodtobetrue,right?Thecatchisthatyouwillhavetrouble
keepingthecacheinsyncwiththeoriginaldata.Thisisespeciallyhardifyouhavescaledoutthe
applicationserver-thatis,setupafarmofapplicationserverseachrunningthesameapplication.
Becareful!Inanenvironmentwhereyouhavescaledouttheapplicationserver,youshould
probablyonlycachestaticdata;otherwise,youriskthemaingoalofyourapplication,namely
correctness.
Whenyouonlyhaveoneapplicationserver,youcanuseserver-sidecachingforsemistaticdata
too,aslongasallupdatesofthedataaredoneviatheapplicationserverandnotdirectlytothe
datasource.However,youmustbeverycareful.Irecommendthatyouskipcachinguntilyou
reallyneedit,andanalyzethecompletepicturethoroughlybeforeusingcaching.Goingtothe
databaseeachtimeisusuallyaquick-enoughsolution.Itisalsosimpleandhasfewerrisks.
Note
Icreatedseveralcachingsolutionsintheyearsbefore.NET.Therearetwoarticlesyou
canreadmoreaboutthetestedmethodsandconclusions,namelyServer-SideCaching
inCOM+andVB
7
andServer-SideCaching(orBuySpeedwithCache).
8
New Server-Side Caching Potential with .NET
Withthehelpof.NET,twonewtechniquesopenupthataregreatforserver-sidecaching,one
beingtheuseoftheCOM+ComponentServiceObjectPoolingandtheotherbeingtheuseof
ADO.NETDataSetsforcarryingcomplexstructuresofdata,forexample,inpooledobjects.Youcan
alsousesharedpropertiesinsteadofpooledobjects,butthenyouhaveonlyoneitemforwhich
youhavetowritethread-safecodeonyourownwhichisalwaysdangerous,andthisalsomight
becomeabottleneck.Withpooledobjects,youcanadjustthesizeofthepooltodecrease
contention,butthenyouhaveanewproblemwhenthecacheistobeupdated,becauseallcached
itemsmustbeupdatedatthesametime.
Note
It'sveryapparentwhenyouthinkaboutcachingthatthereisnosuchathingasafree
lunch.Ifyousavesomehere,youpaythere.Ifyouuseobjectpoolingandhaveseveral
objectsinthepool,youwillalsohavethesamedataseveraltimesinmemory.Ifit'salot
ofdatatocache,havingseveralinstancesofitisawasteofresources.
Finally,anevenbetterplacetousecachingthanintheBusinesstierisintheConsumertier,for
exampleinASP.NETorinthelocalexecutable.Byusingcachinginthistier,youconserve
resourcesattheapplicationserver,andoperationswherecacheddataareusedwillbeevenfaster.
Forserver-sidecaching,youshouldmostprobablyonlycacheshareddata,butit'sallrightto
cachepersonaldataifitisheldattheuser'sworkstation.
Letmestateagainthat therecanbeenormousbenefitstoserver-sidecachinginsomesituations.
Youactuallybuyspeedandpaywithmemory.However,therisksarealsoenormous,sodon'tuse
cachingbeforeyouhavecompletelyunderstoodtheconsequences.
Dealing with Schema Changes
YetanothertopicthatI'dliketodiscuss(andhopefullyprovidealittleinspirationalongtheway)is
howtopreparethecodeforfutureschemachanges.Soonerorlater,andusuallysooner,the
databaseschemawillchange.Makesurethatyouarepreparedsothatyoudon'thavetochange
thecodeateveryschemachange,orevenworse,thatyourcodebreaks.
Asalways,informationhiding,codegeneralization,andautomatictestingareyourfriendswhenit
comestodealingwithchanges.Inthissection,Iwilldiscussacoupleofothertechniquesthatyou
canconsider.
Using User-Defined Data Types
IfyouuseUser-DefinedDataTypes(UDDTs)foryourdatatypes,bothinthetablesandinthe
storedprocedures,itwillbemucheasiertoincreasethelengthofaVARCHAR,forexample,and
havethechangetakeeffectinallthestoredprocedureswiththeminimumamountofwork.
However,thiswillnotsolvethewholeproblem.Forexample,itwon'thelpyouthatmuchifyou
wanttochangefromINTEGER toUNIQUE IDENTIFIER,becausealotofthecodemightassume
INTEGER.
Note
UDDTscan'tbeusedwiththeTABLE datatype,whichisunfortunatebecausetheTABLE
datatypeiscrucialforUDFs.
WhenitcomestoVBcode,UDDTsareoflittleuse.YoucanmapthemupasstructuresinVBtoo,
butIhaven'ttriedthisbecauseitdoesn'tfeellikeastraightforwardproposal.Infact,asfarasI
know,thistopichasn'tbeeninvestigatedmuch,andnoreallygoodproposalsexist.
Using Too-Large CHAR and VARCHAR Parameters
Onestrategyforfindingbugs-becauseofchangeddatabaseschemas,forexample-istousetoo-
largeCHAR andVARCHAR parametersforthestoredprocedures.Ifweassumethatthedescription
columnisofdatatypeVARCHAR(50),itcanthenbedeclaredasVARCHAR(51) inthestored
procedure.Subsequently,thefirstthingtodointhestoredprocedurewouldbetoassertthatthe
DATALENGTH() ofthe@description parameterislessthan51.
InormallyuseUDDTs,sotousetheapproachdiscussedhere,IwouldhavetohavetwoUDDTsfor
eachstringdatatype-onethatisthecorrectlengthtouseforthetableandonethatisonesign
largertousefortheparameters.
AbetterapproachisprobablytoletsomeoftheAddParameter() methodstakeasize parameter
aswell(andthatisexactlywhatIdid,asyoucanseeinFigure8.1).Thenit'seasytocheckthat
theprovidedstringvalueisn'tlargerthanthesize,andifso,raiseanexceptionorletanassertion
signal.Toavoidcreatingamaintenanceproblem,youneedtoholdmoremetadatainyour.NET
componentsaboutthestoredproceduresandtheirparameters,whichisexactlywhatthenext
sectiondiscusses.
Dragging Out Parameters from Stored Procedures and Using Them as
Constants
AsyousawinListing8.5,whenAddParameter() wascalled,Ihadconstantsforalltheparameter
namesinthestoredprocedures.ThenamesareveryimportantbecauseIusenamedparameters.
AsimpleapproachtousefordeterminingwhetheranyparameternamesthataffecttheVBcode
havechangedistodragoutalltheparameters(bothnames,datatypes,andsizes)forthepublic
storedprocedureswithautilityandcreateoneormoreclasseswiththeparametersasconstants.
Theclassesshouldnotbewrittentoatallbyhand.
Subsequently,when youwanttocheckthatthestoredprocedureisasyouwouldexpect,youjust
reruntheutilityandcomparethenewresultoftheutilitywiththeoldclassestoseewhetherthe
classesareidenticaltothepreviousones.Ifitisn't,you'llhavetodeterminewhethertheonly
differencesarenewlyaddedparameterswithgooddefaultvalues.Thisissothatthenew
parametersdon'thavetogetvalueswhenthestoredproceduresarecalledfromoldVBcodethat
isunawareofthenewparameters.
InListing8.5earlierinthischapter,yousawthatIusedconstantscalled,forexample,
Sprocs.Parameters.description andSprocs.ParamterSizes.description,andthatwasan
exampleoftheapproachdiscussedhere.
Using Views and/or UDFs
ViewsandUDFscanserveverywellinmakingmanyschemachangestransparenttothestored
procedures.Often,itfeelslikeoverkilltomapeverytablewithoneormoreviews/UDFs,butit's
wellworththeeffortwhenchangesneedtobemadeinthetablestructure.
Evaluation of My Proposal
It'snowtimetoevaluatethedataaccessproposalIpresentedinthischapter.I'llevaluatethe
proposalnotonlyinitsuseofanSQLscript,butalsoasahelperthatcanbeexpandedtousein
waysotherthangeneratingSQLscripts.
Theproposalfocusesonreducingroundtripsbetweentiers,whichisverygoodforflexibilitywhen
itcomestodifferentphysicalrestrictionsandpossibilities.However,becarefulthatyoudon't
introduceprocessboundariesbetweenthelayersintheBusinesstierbecausethenthearchitecture
willnotbeagoodone.(Thearchitectureitselfagreeswiththistoo.Processboundariesshouldbe
betweentiers,notbetweenlayers.)
Performanceandscalabilityliesattheheartoftheproposal.Atthebook'sWebsiteat
www.samspublishing.com,youcancomparethethroughputbetweenordinaryADO.NETcodeand
mydataaccessproposal,bothforwhenseveralstoredprocedurecallsaremadeinasingle
transactionandwithonly onestoredprocedurecallandnoexplicittransaction.
Scalabilitywillincreasewithmyproposalbecausethetransactionswillbeshorter,andtherewillbe
noroundtripsduringatransaction.Shortertransactionsmeanshorterlocks,whichinturn
decreasescontention.
Idon'tseeanymajoradvantagesordisadvantageswiththeproposalregardingreliability,
reusability,testability,andinteroperability,butIthinktheeffectondebuggabilityispositive.I
mentionedearlierinthechaptertheeasywaytoautomaticallytraceparametervaluesandtoruna
completescenariobycopyingthescriptandexecutingitoverandoveragaininSQLServerQuery
Analyzer.
Maintainabilityisincreasedthankstothefactthattheproposalisconstructedasahelperroutine.
(Thiseffectwillbeachievedbymostotherhelperstoo,ofcourse.)Theproductivityisincreased
duetothehelper,butalsobecausedebuggabilityisincreased.Isuggestyouwriteacoupleof
generatorsthat,forexample,generateallthecodeforcallingastoredprocedure,toincrease
productivityevenmore.
What's Next
Thenextandfinalchapterwilldiscusserrorhandlingandconcurrencycontrol.Itcanbeextremely
trickytohandleerrorscorrectlyinallsituations,especiallyinT-SQLcode, andthereisalotto
considerregarding.NETcodetoo.We'llstartbydiscussingerror-handlingtemplatesforcertain
situationsandwillthenlookatpreparingfortypicalerrorssoyoudon'thavetofindthemoutthe
hardway.Finally,we'lldiscusshowtodealwithconcurrencycontrolindisconnectedscenarios,
withbothoptimisticandpessimisticsolutions.
References
1.S.KirkandJ.Boylan.Tech Ed 2001: .NET Application Architecture Panel Discussion, June
2001,http://msdn.microsoft.com/library/en-us/dnbda/html/bdadotnetarch10.asp.
2.J.Nilsson.Round Trips Are Evil-Reduce Them! PinnaclePublishing'sVisual Basic Developer,
July2001,http://www.pinnaclepublishing.com/VB.
3.T.Ewald.Transactional COM+: Building Scalable Applications.Addison-Wesley;2001.
4.F.Buschmann,R.Meunier,H.Rohnert,P.Sommerlad,andM.Stal.Pattern-Oriented Software
Architecture: A System of Patterns.Wiley;1996.
5.E.Gamma,R.Helm,R.Johnson,andJ.Vlissides.Design Patterns: Elements of Reusable Object-
Oriented Software.Addison-Wesley;1995.
6.DevelopmentAcceleratorsfor.NET.NopublicWebaddressyet.
7.J.Nilsson.Server-Side Caching in COM+ and VB.
http://www.vb2themax.com/HtmlDoc.asp?Table=Articles&ID=360.
8.J.Nilsson.Server-Side Caching or Buy Speed with Cache.
http://www.vbxml.com/conference/wrox/2000_vegas/text/jimmy_cache.pdf.
Chapter 9. Error HandIing and Concurrency ControI
IN THIS CHAPTER
N MyProposalforHowtoHandleErrors
N ExceptionstoBePreparedFor
N ApproachestoConcurrencyControlforDisconnectedScenarios
N EvaluationofProposals
Ionceheardadevelopersay,"Idon'tjudgethecoolnessofanapplicationfromitsfeaturelist,but
fromhowgracefullyithandlesexceptions.Evenifyou'renotstrivingtowritethe"coolest
applicationever,it'sextremelyimportanttoprepareup-frontforstrategiesrelatingtoerror
handling.Howmanytimeshaveyouworkedonprojectsandtoldyourselfthaterror-handlingcode
canalwaysbeaddedlater?Evenifthishasonlyhappenedtoyouonce,youknowthatadding
error-handlingcodetoyourapplicationasanafterthoughtisverydifficult,error-prone,time
consuming,andalmostneveragoodidea.
Asyouknow,foranapplicationtobeconsideredrobustandreliable,it'sextremelyimportantthat
itcatcheserrorsanddealswiththemeffectively.Thisiseasilysaid,butisoftennotdone
sufficientlyinreal-worldapplications.I'llstartthischapterwithanin-depthdiscussionofhowto
handleerrors,bothinstoredproceduresandin.NETcomponents.Afterthat,Iwilldiscussacouple
oftypicalerrorsthatyourapplicationwillmostlikelycomeacross.Iwilldescribetheseerrorsand
proposeefficientapproachesforhandlingthem.Thechapterendswithalengthydiscussionon
severaldifferentapproachesforconcurrencycontrolofdatathathasbeendisconnectedfromthe
database.I'mobviouslynotgoingtoattempttosayallthereistosayaboutconcurrencycontrol
here,butIwilllookatthemainapproaches.
My Proposal for How to Handle Errors
InChapter4,"AddingDebuggingSupport,wediscussedhowtologerrorstoprovidevaluable
debugginginformationsothataproblemcouldbeunderstoodandtrackeddown.Forthiserror-
loggingfunctiontowork,youmustconsiderseveralpointsinboththestoredproceduresandinthe
.NETcomponents.Forastart,errorsmustbecaught.Therefore,let'sstartthischapterbytakinga
closelookathowerrorhandlingcanbeimplementedefficientlyinstoredprocedures.Inthis
discussion,you'llseetheobviousdifferencesbetweentheT-SQLandVisualBasic.NETlanguages
whenitcomestoerrorhandling.
Handling Errors in Stored Procedures
Therearefourdifferenttypesofcasesthatyouhavetothinkaboutwhenyouarehandlingerrors
instoredprocedures.Theseare:
N EXEC ofstoredprocedure
N INSERT/UPDATE/DELETE
N SELECT
N Violationofbusinessrule
Inthischapter,Iwillincludetemplatecodethatyoucanuseforhandlingerrorsinstored
proceduresineachcasebut,asusual,theappropriatenessofthetemplatecodedependsonyour
particularsituation.Don'tuseitwithoutproperconsideration.However,beforeweinvestigate the
fourdifferentcasesforerrorhandlinginstoredprocedures,let'slookatthegeneralapproachthatI
useforerrorhandlinginstoredprocedures.
The General Approach for Error Handling in Stored Procedures
InChapter5,"Architecture,IsharedwithyouthetypicalcodestructurethatIuseforstored
procedures.Inthissection,I'dliketodiscusssomedetailsofthecodestructurethatconcernerror
handling.Listing9.1describesafewvariablesneededfortheerrorhandlingtowork.Ihavetalked
aboutthosevariablesinearlierchapters,butIthinkabriefrecapisinorder.
First,Ihaveavariable,@theSource,forstoringthenameofthecurrentstoredprocedure.Then
therearetwovariablesforstoringinformationabouttheerror,@anError and@anErrorMessage.
@anError isusedforstoringtheerrorcode,while@anErrorMessage isusedforsavingcontext
informationabouttheproblemsothatyoucandeterminewhereinthestoredproceduretheerror
occurred,becausethesameerrorcanoccurinseveralplaces.
@theTranCountAtEntry isusedforstoringthenumberofactivetransactionswhenthestored
procedureisentered,while@aReturnValue isusedforcapturingthereturnvaluefromanEXEC call
toasecondarystoredprocedure.(Quiteoften,@aRowCount isalsoused.Itisusedfortrackinghow
manyrowswereaffectedbyaSELECT,INSERT,UPDATE,orDELETE statement.)
Listing 9.1 Error-Handling Related Variables
DECLARE @theSource uddtSource
, @anError INT
, @anErrorMessage uddtErrorMessage
, @theTranCountAtEntry INT
, @aReturnValue INT
Asyouhaveseeninseveralchaptersofthisbook,IuseanExitHandler attheendofall my
storedprocedures.Aftereach"dangerousstatement,Ichecktoseewhethertherewasanerror.If
therewas,@anError willgettheerrorcodeandthenGOTO theExitHandler,asshowninListing
9.2andlaterinListing9.3.
Listing 9.2 The ExitHandler of a Stored Procedure: Part 1
ExitHandler:
IF @theTranCountAtEntry = 0 AND @@TRANCOUNT > 0 BEGIN
IF @anError = 0 BEGIN
COMMIT TRAN
END
ELSE BEGIN
ROLLBACK TRAN
END
END
Let'slookatthefirstpartoftheExitHandler.Asyou'llsee,Istartbyinvestigatingwhetherthe
storedprocedurewillendatransaction.Iusethe@theTranCountAtEntry variabletodetermine
whethertherewasanactivetransactionwhenthestoredprocedurestarted.Iftherewasn't,this
storedprocedureisresponsibleforthetransactionitselfandwillenditifitisstillactive.Weknow
whetherthetransactionisactivethrough@@TRANCOUNT.
Note
Thetransactionmightbeinactiveifitwasexplicitlyendedearlierinthestoredprocedure
tomakethetransactionasshortaspossible.Evenifthetransactionwasexplicitlyended
earlierinthestoredprocedure,wewillstillhavetheendtransactionsectioninthe
ExitHandler.Thisisbecausewemightencounteranerrorbeforetheexplicittransaction
ending hasbeenreached,andSQLServerwillensurethatitisharmfultoleavethe
storedprocedureif@@TRANCOUNT hasadifferentvaluethanwhenthestoredprocedure
wasentered.
Assumewhenthestoredprocedureconcludesthatthetransactionhastobeended.Then@anError
isinvestigated,soadecisioncanbemaderegardingwhetherCOMMIT TRAN orROLLBACK TRAN
shouldbeused.
InthesecondpartoftheExitHandler,showninListing9.3,therewillbeacalltothestored
procedureJnskError_Raise() ifthereisanerror.Iwilldiscusstheimplementationandreasonfor
JnskError_Raise() laterinthischapter,butfornowyouneedtoknowthatitsmaintaskisto
performaRAISERROR() sothattheinformationontheerrorcanbecaughtbythe.NETcomponent
andusedforlogging.Finally,theExitHandler isendedwithatracecallandthe@anError is
returnedtothecaller.
Listing 9.3 The ExitHandler of a Stored Procedure: Part 2
IF @anError <> 0 BEGIN
EXEC JnskError_Raise @theSource
, @anError, @anErrorMessage
END
EXEC JnskTrace_End @theSource
RETURN @anError
Reasons for Using Both RETURN() and RAISERROR()
YoumightthinkthatI'musingbothasafetynetandawirebecauseIusebothRAISERROR() and
RETURN() oftheerrorcodewhenanerrorisencountered.Iusedtothinksotoo,butthisisstillthe
bestsolutionI'mawareof.Firstofall,thecallermustbeawareofanerrorsothatitcantakethe
correctaction.Forexample,thecallerprobablyshouldn'tmakeanotherstoredprocedurecallafter
anerror, butshouldjustgototheExitHandler insteadandendthestoredprocedure.Second,I
wanttohaveasmuchcontextualinformationaspossiblefortheloggingsolution.
UsingRETURN() forcommunicatingtheresultofastoredprocedureisconsideredadefacto
standardamongT-SQLprogrammers.TheRETURN() valuecanalsobeusedfromthe.NET
component,whichwouldthenknowwhatwentwrong,althoughtheinformationwilloftenbe
vague.Forexample,thefollowingquestionscan'tbeanswered:
N Whereinthecallstackofthestoredproceduresdidtheerroroccur?
N Whicherrorsdidtheinvolvedstoredproceduresraisefromtheerrorand"up?(thatis,the
errorstack)
N Whereinthestoredproceduredidtheerroroccur?
Thislastquestionisaninterestingoneifthesameerrorcouldoccurinseveralplacesinonestored
procedure,whichisacommonsituation.
TheotherapproachistouseRAISERROR() insteadofRETURN().Thissolvestheproblems
concerninglackofcontextinformation.However,RAISERROR() introduces newproblemsinthe
contextofstoredprocedures.TherearetwowaystouseRAISERROR().Youcaneitheruse
RAISERROR() withcustomerrorcodes(whichmeans50000orhigher,andthecodemusthave
beenaddedinthesysmessages table),oryoucanuseRAISERROR() withastring.
Forexample,assumethatyoucatchthe@@ERROR foraduplicatekey(theduplicatekeyerrorcode
is2627)andwanttotellthecallingstoredprocedureabouttheerror.Theproblemsyouwill
encounterare
N Youcan'traisethaterrorto thecallerbecauseRAISERROR() maynotuseerrorcodesofless
than50000.
N Ifyouputtheerror2627togetherwithadescriptionofwheretheproblemisinastringand
usethatstringwithRAISERROR(),thecallerstoredprocedurecan'tgettothestring.
Typically,thecallerwon'tcatchanerroratall,butyoucanatleasthavethateffectbyusing
WITH SETERROR withRAISERROR() togive@@ERROR avalue.
Icouldcreatenewerrorcodesbyjustadding50000toeachoneofthosethatalreadyexistsin
sysmessages.Icouldthencatchreservederrors,raisethemagainbytakingtheiroldvalueand
adding50000,andthenuseWITH SETERROR.(InthiscaseIwouldalsohavetoaddmycustom
errorcodestosysmessages.)TheadvantagewouldbethatIneverhavetocheckthereturnvalue
ofastoredprocedurebutonly@@ERROR,butIfindthistechniquetobeclumsyandabitofahack.
Thus,myconclusionremainstousebothRETURN() andRAISERROR() andletthemsolvedifferent
problems,sotospeak.Thissolutionworksverywell,althoughIdohavetocheckforbothreturn
valuesand@@ERROR afterastoredprocedurecall.
AnotherreasonforusingRETURN() tocommunicateproblemsbacktothecomponentsisthatwhen
SQLServerraisesanerror(suchas2627becauseofduplicatekeys,forexample),itwillbevisible
tothecomponentsasanexception,evenifthestoredprocedureispreparedfortheproblemand
handlesitgracefully.Inthiscase,theRETURN() willsend0 andthecomponentknowsthatthe
exceptioncanbeskipped.
Note
YoucandecreasethenumberofsituationsinwhichSQLServerraiseserrorsthatthe
componentshouldn'tseebytakingproactiveactions.BeforeyoudoanINSERT thatyou
knowmightgiveaduplicatekeyerror,youcancheckthesituationwithaSELECT first.
Let'snowtakealookatthefourdifferenttypesofcasesthatyouhavetothinkaboutwhenyou
arehandlingerrorsinstoredprocedures.I'llstartwiththefirsttypeofcase-errorhandlingafter
EXEC ofastoredprocedure.
Error Handling After EXEC of a Stored Procedure
Thebuilt-inerrorhandlinginT-SQLis,toputitnicely,outdated.Withit,youhavetocheck
@@ERROR aftereachstatementtoseewhetheritisdifferentfrom0.Ifitis,aproblemhasoccurred.
Thisfeaturemakesthe T-SQLcodeclutteredandunnecessarilylong,butyoudon'thaveachoice-
youmust checkforerrors.Inaddition,@@ERROR willbesetto0aftereachsuccessfullyexecuted
statement.Thismeansthatyoucan'tcheck@@ERROR andthenusethevalueafterward. Afteryou
checkthevalue,itissetto0.
InListing9.4,youcanseehowmytypicalcodelooksafterastoredprocedurecall.Thecalled
storedprocedure'sreturnvalueiscaughtin@aReturnValue forlateruse.Then@@ERROR isstoredin
alocalvariable called@anError.Ifthe@anError or@aReturnValue isdifferentfrom0,therewasa
problemofsomekind.If@anError wasequaltozero,@aReturnValue musthavebeendifferent
fromzero,and@anError getsthatvalue.Finally,GOTO isusedtojumptotheExitHandler.
Listing 9.4 Error Handling After a Stored Procedure Call
EXEC @aReturnValue = Errand_Insert
@id, @description, ...
SET @anError = @@ERROR
IF @anError <> 0 OR @aReturnValue <> 0 BEGIN
SET @anErrorMessage = 'Insert problem...'
IF @anError = 0 BEGIN
SET @anError = @aReturnValue
END
GOTO ExitHandler
END
It'sobviousthatI'mparanoidbecauseIcheckfor@@ERROR afterastoredprocedurecall.You
shouldalmostneverfindthat@@ERROR isdifferentfrom0afterastoredprocedurecall.However,
youmightfindthistobethecaseifthecalledstoredproceduredoesn'texist,althoughthisshould
neverhappeninanoperationalsystem,ofcourse.Inanycase,it'sbettersafethansorry,andthe
effectofcheckingfor@@ERROR afterastoredprocedurecallcanbedramatic.Forexample,assume
thatyouhavetwostoredproceduresthatmustbecalledinthesametransactionand,forsome
reason,somebodybymistakehaschangedthenameofoneofthetwostoredprocedureswithout
changingallthecalls.Ifonly@aReturnValue isusedforcheckingforsuccess,thetransactionwill
probablyCOMMIT,eventhoughoneofthestoredprocedureswasn'tfound.
Error Handling After INSERT/UPDATE/DELETE Statements
TheerrorhandlingwillbesimilarinthecaseofINSERT,UPDATE,andDELETE statements,butitis
slightlydifferentfromstoredprocedurecalls.ForINSERT,UPDATE,andDELETE,youdon'thavea
returnvaluetocheck;instead,@@ROWCOUNT tellsyouhowmanyrowswereaffected.Listing9.5
showsanexample oftheerrorhandlingusedafterINSERT,UPDATE,andDELETE statements.Asyou
cansee,Ineedtocatchboth@@ERROR and@@ROWCOUNT inonestatement,soIuseSELECT instead
ofSET.IfIdon'tdoitlikethis,butfirstcatchthe@@ERROR valueinstead,forexample,@@ROWCOUNT
willbelostwhenItrytograbit.
Listing 9.5 Error Handling After INSERT, UPDATE, and DELETE Statements
INSERT INTO errand
(id, description, ...)
VALUES
(@id, @description, ...)
SELECT @anError = @@ERROR, @aRowCount = @@ROWCOUNT
IF @anError <> 0 OR @aRowCount = 0 BEGIN
SET @anErrorMessage = 'Problem with insert...'
IF @anError = 0 BEGIN
SET @anError = 80001 --Unexpected error.
END
GOTO ExitHandler
END
Intheerrorhandlingforastoredprocedurecall,ItypicallywanttokeeptheerrorcodeIfind.In
thecaseoferrorhandlingforINSERTs,UPDATEs,andDELETEs,Ikeeptheerrorcodeasitis
because@@ERROR wasdifferentthan0.Iftheerrorisbecause@@ROWCOUNT is0,Ihavetodecide
whaterrorcodetosetonmyown. Acommonreasonforwhy@@ROWCOUNT equals0 afterUPDATE
andDELETE isthattherewasaconcurrencyconflict,whichIwilldiscusslaterinthischapter.
Note
Youcan,ofcourse,redefineanerroronitswayupthecallstacksothattheBusinesstier
doesn'thavetobeawareofsomanypossibleerrors.Still,Ihavethesameproblem
betweentheConsumertierandtheBusinesstier,andthatiswhereIfocusmyeffortsat
handlingtheproblem.
How to Choose Custom Error Codes
Asyouknowbynow,youarefreetochooseerrorcodesfrom50000and
greater.Below50000isreservedforSQLServer'sownuse.Ifyouwant,
youcansaveerrorcodesinthesysmessages tableandthenuse
RAISERROR() withthecodetogetthetextfromsysmessages.Iactually
prefertouseatableofmyownwithmyownerrormessages,becauseI
prefertouseacustomstringtoRAISERROR() insteadofanerrorcode.
ThecustomstringisformattedasXMLtomakeiteasyforthelogging
solutiontofindalltheinformationitneedsforevery error.
Error Handling After a SELECT Statement
Gettingacatchable@@ERROR presentsonlyhypotheticalrisksafterSELECT statements.Mosterrors,
suchasincorrectcolumnnames,willabortthebatchsoyoucan'tdealwiththe@@ERROR.Inany
case,therearesomeinstanceswhenyoucanfindavaluein@@ERROR differentfrom0,soIuse
errorhandlingforitallthetime.Listing9.6showsanexampleoferrorhandlingafteraSELECT
statement.
Listing 9.6 Error Handling After a SELECT Statement
SELECT e.id, e.description
FROM errand e
WHERE e.id = @id
SET @anError = @@ERROR
IF @anError <> 0 BEGIN
SET @anErrorMessage = 'Problem with select...'
GOTO ExitHandler
END
Note
Examplesof@@ERROR problemsyoucancatchafteraSELECT statementare:
N ArithmeticoverflowaswhenyouCAST avaluetoatoo-smalldatatype.
N Divisionbyzero.
N Timeoutwhenalockcouldn'tbegranted,afterLOCK_TIMEOUT hasbeenSET toa
valuedifferentthan0.
Althoughit'snotcommon,itisalsosometimesanerrorforSELECT if@@ROWCOUNT is0(thatis,it
mightbeanerroriftheSELECT doesn'tfindanyrows).Inthesecases,youcaneasilyswitchtothe
constructionusedforINSERT,UPDATE,andDELETE instead.
Error Handling After Violation of a Business Rule
Finally,thefourthtypicalsituationforerrorhandlingisoneinwhichyoufindthatthereisa
problem,forexample,becausethevaluesofsomeoftheparametersaren'tasexpected.Inother
words,abusinessrulehasbeenbroken.InListing9.7,youcanseeexamplecodeforsucha
situationandhowtheerrorishandled.Asyoucansee,thehandlingofthissituationis
straightforward.
Listing 9.7 Example Code for When a Business Rule Has Been Broken
IF @payment < @theExpectedPayment BEGIN
SET @anError = 81001 --For example...
SET @anErrorMessage =
'The provided payment is too low.'
GOTO ExitHandler
END
Note
NotethatinListing9.7,itwouldbevaluabletoaddtherealvaluesofthetwovariablesto
the@anErrorMessage stringaswell.Providinginformationlikethisisinvaluablefor
quicklydetectingwhattheproblemis.Theruntimecostismostoftennegligible,atleast
whenitisnotthenormalsituation,butonlyanabnormalexception.
An Additional Stored Procedure: JnskError_Raise()
AsyousawinListing9.3,theExitHandler centralizestheraisingoferrorsthroughastored
procedurecalledJnskError_Raise().I'dliketodiscusstheimplementationofthisstored
procedurebeforewelookatexceptionhandlingin.NETcomponents.
Listing9.8showsthefirstpartoftheimplementationofJnskError_Raise().Asyoucansee,two
parametersseemabitobscureatfirst-@specificErrorMessage and@severityLevel.
@specificErrorMessage istypicallythe@anErrorMessage fromthecaller-storedprocedurethat
getsitsvalueasclosetowhentheproblemhasbeenfoundaspossible.(Youhavealreadyseen
examplesofthisinListings9.4,9.5,9.6and9.7)
Listing 9.8 The Implementation of JnskError_Raise(): Part 1
CREATE PROCEDURE JnskError_Raise (@source uddtSource
, @error INT, @specificErrorMessage uddtErrorMessage
, @severityLevel INT = 11)
AS
DECLARE @aBuffer VARCHAR(255)
, @aStandardErrorMessage uddtErrorMessage
SET NOCOUNT ON
SET @aBuffer = ''
IF @error >= 50000 BEGIN
SELECT @aBuffer = j.description
FROM jnskerror_message j
WHERE j.id = @error
END
AsyouseeinListing9.8,the@severityLevel getsthedefaultvalue11,aswasthecaseinListing
9.3.Therewasnoexplicitvalueprovidedforthisparameter(whichismostoftenthecase),sothe
defaultvaluewillbeused.
Then,twolocalvariablesaredeclared.In@aBuffer,thecompletestringwillbeconstructedtobe
usedbyRAISERROR() later.@aStandardErrorMessage istheoppositeofthe
@specificErrorMessage parameter.In@aStandardErrorMessage,IwillstoreastringthatImight
findfrommycustomtableoferrormessages.Assumethaterror80001islabelledasan
"Unexpectederror.Itdoesn'thavetobeSET eachandeverytimeitisused;indeed,theonly
thingthatshouldbeSET whentheproblemoccursiscontext-specificinformation.
Next,ImakeafewinitializationsbeforeIreadfrommycustomerrormessagetabletoseeifthere
isstandardizedtexttobeaddedtotheerrormessage.
Note
AsIhavementionedalready,Icouldhaveusedthesysmessages tableinsteadofmy
custom-builtone,butIliketoaddinformationtothetable,suchasprobablereasons,for
theoperationstafftouse.Thisway,thedocumentationwillalsobeapartofthesystem
andyoucan,ofcourse,outputthe"reasoncolumnintheerrorlogtoo.Youcanalsogive
thiskindofinformationforthereservederrorcodessmallerthan50000inthecustom
table.Thatisnotpossiblewiththesysmessages table.Ifso,youhavetochangethe
implementationofJnskError_Raise() slightlysothatitdoesn'tcheckwhetherthevalue
is50000orgreaterfortheerrorbeforereadingfromthecustomerrormessagetable.
InthesecondpartoftheimplementationofJnskError_Raise(),asyoucanseeinListing9.9,I
formatanXMLstringwithinformationabouttheproblem.ThisXMLstringwillsubsequentlybe
usedasthestringtoRAISERROR() andalsoforthetracecall,signallinganerror.
Listing 9.9 The Implementation of JnskError_Raise(): Part 2
SET @aBuffer = '<errorMessage error="'
+ CAST(@error AS VARCHAR(10)) + '"'
+ ' description="'
+ @specificErrorMessage + '|'
+ @aBuffer + '"'
+ ' source="' + @source + '">'
RAISERROR (@aBuffer, @severityLevel, 1)
EXEC JnskTrace_Error @source, @aBuffer
Listing9.10showsanexampleofthecontentof@aBuffer fromListing9.9.
Listing 9.10 Example of the Content of @aBuffer
<errormessage error="80001"
description="Problem with insert...|Unexpected error." source="Errand_Insert">
NotethatoneproblemwithcentralizingtheerrorraisinginT-SQL,asIdowith
JnskError_Raise(),isthatyouwillgetincorrectsourceinformationwhenyouexecutethestored
procedurefromSQLServerQueryAnalyzer,asyoucanseeinListing9.11.Insteadofthefaulty
storedprocedure,JnskError_Raise() willbementionedastheproblematicstoredprocedureand
thelinenumberwhereRAISERROR() was called.However,Idon'tconsiderthisabigproblem,
especiallybecausethedescriptionstringwillcontaintherealsource.
Listing 9.11 Example of Output in SQL Server Query Analyzer (Shown with Line Feeds)
Server: Msg 50000, Level 11, State 1, Procedure JnskError_Raise, Line 40
<errorMessage error="80001"
description="Programming error. Incorrect parameter!|Unexpected error"
source="a_Errand_FetchList">
Note
Whetherit'sagoodideatosendthecomplete@aBuffer toJnskTrace_Error() isopen
todiscussion,because@source willbeprovidedtwice,sotospeak-bothasaparameter
onitsownandalsoaspartofthe@aBuffer.Thisisaminorproblem,butonethatis
perhapsworthlookingat.
Notethatwhenthereisavaluein@@ERROR differentfrom0,SQLServerwillmakeaRAISERROR()
callforyou,andthatwill,ofcourse,notbeformattedliketheoneinListing9.10.The.NETcode
thatexaminestheerrorstackandwritestotheerrorlogispreparedandwillreformatthat
RAISERROR() messageaccordingly. Thisleadsusnicelyintoexceptionhandlingin.NET,ournext
subjectofdiscussion.
Handling Exceptions in .NET Components
ErrorhandlinginT-SQLishopeless,evenworsethanitwasinVB6.Thankfully,theexception
handlingin.NETisstateoftheart.Certainly,Ithinkexceptionhandlingin.NETalsohasits
caveats.Asyouknow,Ialwaystakeprideintryingtofindissueswitheverything,butexception
handlingin.NETiswonderfultoworkwith.
Mostbooksabout.NETprogrammingincludeachapteronhowexceptionhandlingworksandis
programmedin.NET.I'mnotgoingtodiscussallthedetailsinthissection;instead,Iwillfocuson
thestrategiesparticulartotheserver-sidelayersandapplythetechniquetothearchitectureand
otherpreviousdiscussionsyou'vereadaboutinthisbook.NotethatIusethewords"exception
handlinginsteadof"errorhandlinginthissection.WhenitcomestoVB6andT-SQL,themost
commontermtouseis"errorhandling,butin.NET,it'smostlyreferredtoas"exception
handling.
Note
AsIsaid,thereisawealthofinformationaboutexceptionhandlingavailabletoyou.If
youwanttolearnmoreabouthandlingexceptions,seeDanielAppleman'sMoving to
VB.NET: Strategies, Concepts, and Code
1
andKeithFranklin'sVB.NET for Developers.
2
You
shouldalsoreadEricGunnerson'sarticles,WritingExceptionalCode
3
andTheWell-
TemperedException.
4
Finally,averygoodwhitepaperisKennyJonesandEdward
Jezierski'sExceptionManagementin.NET.
5
I'dliketostartthissectionbyrecappinghowIthinkTry,Catch,andFinally shouldbeused.
Recap of the Basic Structure of Try, Catch, and Finally
AlthoughwediscussedthebasicstructureofTry,Catch,andFinally inChapter5,let'srecapthe
basicideas.Assumethatyouaregoingtousearesource-sayaninstanceofaclasscalled
doErrand andamethodcalledEscalate()-tocheckwhetheranerrandisoldenoughand
importantenoughtoescalatetothenextlevelofproblemsolvers.Inthiscase,theinstantiationof
theobjectisusuallydonebeforethefirstTry clause.Iftheinstantiationfails,thepreviousmethod
inthecallstackwillhandletheexception.ThisissimilartowhenUsing() isusedinC#.
(Instantiationsseldomfail,butifyoufindthisisaproblem,youcanalwaysaddanouterTry block
tothestructureI'mproposinghere.)Whentheresourceisused,itwillbeprotectedbyaTry
block.Finally,aFinally blockwillcallIDisposable's Dispose().
ThisismoreorlesswhatwediscussedinChapter5.ButwhatshouldbedonebetweenTry and
Finally ifthereisanexception?Thissectiondiscussesthisscenariofurther.
Seven Different Approaches for Handling an Exception
Thereareactuallymorethansevenapproachesforhowtohandleanexception,becausethereare
morecombinationsthanthoseI'mabouttoexplain.Ihavechosentodiscussonlysevencases
because,bydoingso,Itouchonallthebasic"ingredients.Beforewegettotheexamples,let's
takealookatthedifferentelementsofexceptionhandlingandthecombinationsthatarepossible:
N Catch- Willtheexceptionbecaughtornot?
N Compensate- Willtherebecodecompensatingoractingbecauseoftheexception?
N Throw- Howshouldtheexceptionberaisedbacktothecallingmethod?Thereareseveral
mechanismsforthrowinganexception.First,youcanuseThrow justtorethrowthe
exception,Throw e tothrowanewexceptionofthesametypethatwascaught,andThrow
New tothrowanewexceptionofanothertype.(Iwilldiscussthedifferenceslaterinthis
section.)
N Inner- Willthecaughtexceptionbewrappedasaninnerexceptionandsentwiththe
Throw sothatthereceivercanseeboththeexceptionandthepreviousexception?
Youcanseethatthereare15combinationsoftheseelementsinTable9.1.Togiveanexampleof
howtosreadthetable,let'stakealookatthethirdexampleinthetable.There,theexceptionwill
becaught,butnocompensatingactionwillbeused.Theexceptionwillberaisedtothecallerby
usingThrow,butwithoutwrappingtheoriginalexceptionasaninnerexceptiontotheThrow
statement.
Table 9.1. Combinations of Exception Handling Elements and Their Validity
Combination Catch Compensate 1hrow Inner
1 No - - -
2 Yes No No -
3 Yes No Throw No
4 Yes No Throw Yes
5 Yes No Throwe No
6 Yes No Throwe Yes
7 Yes No ThrowNew No
8 Yes No ThrowNew Yes
9 Yes Yes No -
10 Yes Yes Throw No
11 Yes Yes Throw Yes
12 Yes Yes Throwe No
13 Yes Yes Throwe Yes
14 Yes Yes ThrowNew No
15 Yes Yes ThrowNew Yes
Let'snowlookatthesevenmosttypicalapproachesforhowtohandleanexception.
Don't Catch
Ifyoudon'thaveaCatch block,youwon'tCatch theexception,anditwillbubbleupthecallstack.
InVB6,thisapproachwasuncommonbecauseofthenecessityofalwaysexplicitlycleaningupall
method-instantiatedresourcesinthemethod.Therefore,errorshadtobecaughtlocallytoallow
themethodtodothecleanupbeforereraisingtheerror.Thisisnotaproblemin.NETbecauseyou
havetheFinally clause,sothecleanupisdecoupledfromthecatchingofexceptions.
Note
Thisisnottosaythatexplicitcleanupisn'timportantin.NET.I'msureyourememberthe
discussionaboutIDisposable fromChapter5.Whenyouinstantiateresourcesthat
implementIDisposable atthemethodlevel,theyshouldalwaysbedisposedofina
Finally block.
Ifyou'renotgoingtodoanythingspecialwithaproblemlocally,thereisreallynopointincatching
theexceptionatall.Ofcourse,soonerorlateryouwillhavetoCatch theexception,butthis
doesn'tnecessarilymeanithastobeatthelowestlevel.
Thissolution-notusingCatch atall-istheeasyone.Let'snowturnourattentiontotechniquesto
usewhenexceptionsarecaught.
Catch and Eat
It'sverysimplejusttoCatch anexceptionandthennotdoanythingaboutit.It'sdoable,butit's
justasdangerous,asitisforotherplatforms.Ifyoutreatveryspecificexceptionsthisway,of
courseitwon'tbeaproblem,butifyouhavealastresortCatch thateatseveryproblem,thereis
potentiallyahighriskoffuturebugsoccurring.
Catch and Compensate
SometimesyoufindthatyoucanCatch anexception,investigateit,andthenusesome
compensatingcodethatdealswiththeexceptionasawholesoyoudon'thavetoraiseanew
exceptiontothecaller.Atypicalexamplewouldbearetrymechanism.
Catch and Throw
WhenyouuseCatch andThrow,youaremoreorlesssayingthatyouhadtoCatch theexception
becauseyouhadtousesomecompensatingcode,butthenyoudon'twanttodealwiththereal
exception.Rather,youwantthecallertotakecareofthatsoyoujustrethrowtheexception.You
willlaterseethatthismightbeacommonapproachfortransactionalservicedcomponentswhen
AutoComplete isn'tused.
Catch and Throw e
Catchinganexception-forexample,As Exception-andthenusingthatvariabletoThrow isoften
abadhabittogetinto.Itmeansthatyoutreatallexceptionsasoneandthenyougivethat
informationtothecaller.Ifyouuseaninnerexception,itispossibleforthecallertofindthe
previousexception,butthisisnotthewaythecallerwantstofindoutwhattheproblemwas.The
callerwillhavetousemuchtoomuchcodeinthissituation.
Note
Ihavedecidedtousee forexceptions,eventhoughitisalsothecommonvariablename
forevents.Itshouldbeobviousfromthecontextwithwhichwearedealing.
Catch and Throw New
WithCatch andThrow New,theexceptioniscaught,butwhenit'stimetoraiseanexception,a
newexceptioniscreatedandthrown.Thissolutionistypicalat"borders,suchasintheclassesin
theApplicationlayer,sothattheconsumerwillonlyseenice,neatexceptions.Onlyshowthe
consumerexceptionsthatyouhavedocumentedascomingfromyouandexceptionsthatrevealas
littleaspossibleaboutyourinnerworkings.Forexample,ifyoulettheconsumerreceiveaserviced
component-relatedexception,youhaverevealedinformationtotheconsumerthatheorshe
shouldn'tknow.
Catch and Throw (in Any Way) with Inner Exception
AslongasThrow isusedwithoutaninnerexception,thereceiveroftheexceptionwon'tknowthat
therewerepreviousexceptionsorwhatthoseexceptionswere.Thisis,ofcourse,unfortunatein
somesituations.(Oneexampleisregardingtheerror-loggingsolutionproposedinChapter4.
Therefore,inmostsituationswhenyouuseThrow afterCatch,youshouldalsowrapthecaught
exceptionasaninnerexception.
Tosimplifyadjustingtotheerror-loggingprotocol,Iproposethatahelperbeusedforraising
exceptions.IcallthehelpermethodJnsk.Instrumentation.Err.Raise(),discussedinthenext
section.
My Centralized Method for Raising Exceptions
Withoutfurtherado,Listing9.12showstheRaise() methodoftheJnsk.Instrumentation.Err
helperclass.
Listing 9.12 Implementation of Jnsk.Instrumentation.Err.Raise()
Public Shared Function Raise(ByVal exceptionToThrow As Exception, _
ByVal caughtException As Exception, _
ByVal log As Boolean, _
ByVal exeOrDllName As String, _
ByVal source As String, _
ByVal userId As String) As Exception
If log Then
Jnsk.Instrumentation.Err.Log(exeOrDllName, _
source, caughtException, userId)
End If
Jnsk.Instrumentation.TraceError(exeOrDllName, source, _
caughtException, userId)
Return exceptionToThrow
End Function
Note
ThecodeinListing9.12looksabitunintuitive.Bepatient;itwillbeexplainedinjusta
fewparagraphs.
Itisworthmentioningthatifthelog parameterisTrue,noinnerexceptionwillbeusedwhen
throwingtheexception.Thereasonisthatyourconsumerswillprobablyreceivetheexception,and
youdon'twantthemtogetalltheinformationaboutpreviousexceptions.
IinitiallyplannedtoletthehelperdotheThrow too,butthemaindrawbackwithhavingahelper
forraisingexceptionsisthattheexceptionstackisaffectedwithmorecallsthanareconceptually
involved.TheThrow willhappeninthewrongmethod,sotospeak.Thiswouldn'tbeaproblemfor
myerror-loggingsolution,becauseIcouldfilteroutcallstomyRaise() method.However,itcan
beaproblemforotherconsumerswhowanttobrowsetheexceptionstack.Apartfromthis,Ifind
therearemainlyadvantagestothissolution.Forexample,logginganexceptiondoesn'tneed
anothermethodcall,acertainexception-handlingprotocolisforced(positively)onthe
components,andifyouwanttochangeexception-handlingbehavior,youhaveonlyoneplaceto
go.Toeliminatethelastdrawback(theexceptionstackbeingaffected),Iusealittlelessintuitive
calltotheRaise() method.InListing9.13,youcanseewhenRaise() isbeingcalledfroma
Catch block.
Listing 9.13 Raise() Called from a Catch Block
Catch e As Exception
Dim aNewException As New Exception(Exceptions.UnexpectedException)
Throw Raise(aNewException, e, _
True, AssemblyGlobal.exeOrDllName, theSource, "")
ThecodeinListing9.13alsohelpsexplaintheimplementationoftheRaise() methodshownin
Listing9.12.Asyoucansee,theThrow isactuallytakencareofbytheCatch blockitself,notby
theRaise() method.Becauseofthis, theexceptionstackisn'taffected,buttheerrorloggingand
tracingisstillcentralizedtotheRaise() method.
InthecodeinListing9.13,thelog parameterisTrue,andaNewException doesn'tgetaninner
exception.ThiscodeisextractedfromanentrymethodintheApplicationlayer.
AlsoworthmentioningaboutListing9.13isthatExceptions.UnexpectedException isaconstant,
containingthevalue"Unexpected Exception".
Note
EventhoughIdidn'tshowithere,thereisalsoaServicedErr classthatusesthe
ServicedTrace classfortracing.IdiscussedthisinChapter4.
Different Strategies for Different Layers
It'sbeenawhilesince IpresentedmyarchitectureproposalinChapter5,"Architecture.You'll
recallthattheBusinesstierhasthreelayers-theApplicationlayer,theDomainlayer,andthe
PersistentAccesslayer.Figure9.1presentsthearchitectureforyourreview.
Figure 9.1. Tiers and layers in the architecture.
Whenweconsiderexceptionhandlingintermsofmyarchitecturalproposal,wemustlookat
whetheralllayersmustcatchexceptionsornot.Inotedearlierthatifyouaren'tgoingtodo
anythingwithanexception,don'tcatchit.Ontheotherhand,soonerorlateryoureachtheentry
point,whichistheApplicationlayer.Youthenhavetodecidewhattheexceptionwilllooklikefor
theconsumer.
Note
It'sprobablyalsothecasethatonlytheApplicationlayerwilllogproblems.Youdecide
thisbygivingTrue tothelog parameteroftheRaise() methodpreviouslydiscussed.
AsIsaidinChapter5,theConsumerHelperlayerwilltranslateexceptioninformationfrom
programminginformationtouser-friendlyinformation.WhenIsaidthat,Iwasthinkingaboutwhat
texttoexpose.Theprogrammeroftheconsumerwantstoseedescriptiveinformationfromthe
Applicationlayer,butonnoaccountshouldyouexposesensitiveinformation.Youneedtostrikea
delicatebalance.
Code with Manual Voting
InChapter5,IrecommendedyouuseAutoComplete() forthemethodsofyourtransactional
classes.However,sometimesyouneedtomanuallyvotefortheoutcomeofautomatic
transactions.Ofcourse,thisiseasytodo,butitalsoforcesyoutoCatch exceptions,evenwhen
youhavepreferredtoleavethattothecallerinstead.
Note
IfyoufollowthearchitecturethatIproposedinChapter5,theneedfornegativevoting,
whichforcesaCatch,won'tbeaproblem,evenifyoucan'tuseAutoComplete().Thisis
becauseyouwillnormallyonlyhaveonemethodtodothevoting,anditwillbelocatedin
theApplicationlayer,whichwillneedtoCatch theexceptionanyway.
Therearetwocommonstylestochoosefromwhenusingmanualvoting.Forsimplicity'ssake,I'm
assumingyouuseSetComplete()/SetAbort() forthevoting.Inthefirstcase,youstarttheTry
blockbyusingSetAbort() andthenthe laststatementoftheTry blockisSetComplete().This
meansthatifyoureachthelaststatementoftheTry block,themethodvotesyes;otherwise,the
voteisno.
ThesecondapproachistomoveSetAbort() toeachoftheCatch blocks.Thisusuallyrequiresa
littlemorework,andthereistheriskthatyoumightforgettoaddSetAbort() tooneCatch block.
Ontheotherhand,thereisalsoabetterchancetocontrolthevoting,becauseyoumightnotwant
tovoteno inthecaseofcertainexceptions.
Writing Your Own Exception Classes
Whenyouwanttoraiseexceptionsthataren'talreadyfoundinthe.NETFramework,youshould
gatherthoseexceptionsassubclassestoacustomexceptionclassthatinheritsfrom
ApplicationException.IfweassumeyoursuperclassiscalledAcmeException,theconsumercan
easilygroupitsCatch blockstoseewhethertheexceptioniscomingfromyourapplication,atleast
iftheexceptionisanuncommonone.
Shouldyouwanttogivetheuserinformationabout,say,severalbrokenbusinessrulesinone
singleexception,thismightbeagoodsituationforaddingcustombehaviortoyourexceptionso
thatyoucanaddalltheprobleminformation.Thisway,theconsumercanfindinformationabout
eachprobleminoneexception.
Note
VB6lackedagoodmethodtocatchunhandlederrorshighupinthecallstack.Wecan
nowdothisthankstotheUnhandledException eventin.NET.Itmightbeusefulasalast
resort,butrightnowIdon'treallyseetheneedforit,becauseIletmyApplicationl ayer
classescatchallexceptionsandthrowappropriateexceptionstotheconsumer.
Exceptions to Be Prepared For
Insomeofmysamplecode,inarticlesandcoursematerial,Iforceadivisionbyzeroonpurposeto
showwhathappenswhenanexceptionoccurs.Inreality,divisionbyzeroisn'toneofthemost
significantproblemsyou'llface,becausemostdevelopersknowhowtopreventit.Inthissection,
I'lldiscussthreedifferentexceptionsthatyoucan'tpreventcompletely,aswellastherelated
problemsforwhichyoushouldbepreparedtodeal.BecauseI'mdiscussingthemhere,thereisno
excuseforwaitinguntilyouactuallyhavetheproblemsinyourapplicationtoaddexception
handlingtodealwiththem.Inaddition,you'llseethattheConsumerHelperwediscussedin
Chapter5 isourallywhenitcomestohelpinguswithallthreeexceptionsituations.
Deadlock (1205)
EventhoughyoumayhaveusedallthetipsIgaveyouinChapter6,"Transactions,tolessen the
riskofdeadlocks,youwillsometimesexperiencethemanyway.Soonerorlater,youwillgeterror
code1205 fromSQLServer,lettingyouknowthatyouhavebeenselectedasadeadlockvictim.
Unfortunately,theerrorhandlinginyourstoredproceduresisofnohelpwhatsoever,andthe
storedprocedureinwhichthedeadlockoccurredwillbeinterruptedandsowillthecallingbatch.
Therefore,you'rerightbackinthePersistentAccesslayeragain.
Note
YouwillgetaSystem.Data.SqlClient.SqlException andtofind1205,youhaveto
investigatethesqlErrorsinthesqlErrorCollection fortheSqlException.
IfyoudouseCOM+transactions,thetransactionisdoomed,andyouhavetoleavethetransaction
stream.YoudothisbygoingallthewaybacktotheConsumerHelper.
TheConsumerHelperlayerclasscanthenrestartthescenarioandcalltheApplicationlayerclass
again,andanewCOM+transactionwillbestarted(iftheApplicationlayeristransactional).The
ConsumerHelperwilloftenrepeatthisbehavioracoupleoftimesafterarandom-lengthpause
beforeitgivesupandtellstheuserthatthereisprobablysomethingverywrongbecausethe
transactionhasbeeninterruptedoverandoveragain.
What Is Deadlock?
Asimplewaytodescribeadeadlockisthattwotransactionsareboth
requestingresourcesexclusivelyheldbytheothertransaction.Neither
transactionwillreleaseanyresourceuntilithasgrabbedthesecond
resource,butthiswon'thappen;thus,deadlockoccurs.
Timeout
AsimilarsituationtodeadlockthatisfoundinaResourceManager(RM)iswhentheCOM+
transactiontimesout.TheexceptionwillshowupasSystem.Data.SqlClient.SqlException:
"Timeoutexpiredinyour.NETcomponent.Thismaybebecausethereisadistributeddeadlockor
becauseyouhavesetthetimeouttoavaluetooshortforthecurrentload.
Note
Thedefaultvaluefortransactiontimeoutof60secondsisnotappropriateforoperational
applications,butonlyfordebugging.Youshouldprobablydecreaseitto,say,3or5
secondsinstead.Theshorterthebetter,untiltimeoutsonlyoccurbecausethegiven
timeoutattributeistoolow.
Whateverthereason,thereisnothingyoucandoabouttheprobleminthecurrentCOM+
transaction.Instead,youhavetotrusttheConsumerHelperlayerclasstotakecareoftheproblem
again,byretryingthecalltotheApplicationlayerclassacoupleoftimesbeforegivingup.
Note
Unfortunately,youwon'tgettheexceptionuntiltheCommandTimeout oftheSqlCommand
objecthasbeenreached,andtheexceptionisalsothesameaswhenthe
CommandTimeout hasbeenreachedbecauseofanotherreasonthanthattheCOM+
transactiontimeouthasoccurred.
Shutdown
Sometimes,theCOM+applicationterminatesandyouget
"System.Runtime.InteropServices.COMException:TheRPCserverisunavailable.(0x800706BA)
whenyoutrytousetheApplicationlayerinstancefromtheConsumerHelperlayer.Thereasonfor
thisproblemcouldbethatthereisaseriousproblemandCOM+hasdecidedtoterminatethe
COM+applicationandrestartit,orelseanadministratorhasdecidedtoshutdowntheapplication.
Note
Itdoesn'tmatterifyouuseJust-In-Time(JIT)activationornot,becausethisproblemcan
showupinbothsituations.Ofcourse,theriskofthisexceptionoccurringisgreaterifyou
keepareferenceduringaperiodoftimeanddon'tteardownthereferencebetween
requests,butitdoesn'tmatter.Youshouldpreparefortheexceptionanyway.
Inanycase,theApplicationlayerinstancewillbelostfortheConsumerHelperlayerclass.Once
again,theConsumerHelperlayerclasswillhelpyououthere,andthesolutionissimple-the
ConsumerHelperlayerclassjustcreatesanewinstance.Exactlyasinthecasewiththedeadlock
situations,theConsumer Helperlayerclasswillretryacoupleoftimesbeforegivingup.
Approaches to Concurrency Control for Disconnected Scenarios
Theterm"concurrencycontrolisabroadone.Inthenextcoupleofpages,Iwilldiscussitinvery
specific,butstillcommon,scenarios-optimisticandpessimisticconcurrencycontrol.
Inthefirstscenario,calledoptimisticconcurrencycontrol,youreaddatafromthedatabase(let's
callthistimeA),butdisconnectthedatasoalllocksarereleased.Then,whentheuserhasmade
somechangestothedataanditisreadytobewrittenbacktothedatabase(attimeB),youwant
tobesurenottosavechangesthatoverwritechangesmadebysomeoneelsetotheverysame
rows(betweentimeAandB).Thisprocessiscalledoptimistic concurrency control becauseitis
mostlikelypossibletosavechangesbecausenobodyelsehasmadeinterferingchangestothe
samedata.
Note
Neverholdlocksinthedatabasewhilewaitingforuserinteractions.Keepinglockperiods
asshortaspossibleisperhapsthesinglemostimportanttipforachievinghighscalability.
Inpessimisticconcurrencycontrol,youwanttobesurethatyouwillbeabletochangewhatyou
havereadfromthedatabasewithouttherebeingariskthatsomeoneelsehasmadechangesthat
makeitimpossibleforyoutoupdate.Iwilldiscusspessimisticconcurrencycontrollaterinthis
chapter,butfornow,let'slookatoptimisticconcurrencycontrol.
Note
Ifyouwanttoreaduponconcurrencyingeneralfordatabaseapplications,agoodsource
wouldbeageneraltextbookondatabases.Forexample,C.J.Date'sAn Introduction to
Database Systems
6
orThomasConnollyandCarolynBegg'sDatabase Systems: A
Practical Approach to Design, Implementation, and Management.
7
Optimistic Concurrency Control
Youmaybewonderingwhywehavetothinkaboutoptimisticconcurrencycontrolatall.Well,
sometimesitmightbeacceptabletonotcare,butusuallythereisarealneedforaconcurrency
controlmechanism.Let'sinvestigateoneoftheproblemsassociatedwithoptimisticconcurrency
control,calledtheLostUpdate problem,butthistimeinasituationinwhichyouaredisconnecting
fromthedatabasebetweenreadandwrite.
InTable9.2,youcanseethatIhavetwotransactionsworkingwiththesamedata(calledx inthe
example).TransactionAisreadingthevaluex attimet1;TransactionBreadsthesamevalueat
t2andsavesachangetothevalueatt4.WhenTransactionAisgoingtosaveitschangetothe
samevalueatt6,thevalueinthedatabaseisn'tthesamevalueasitwaswhenTransactionAread
thevalue att1.
Table 9.2. Example of the Lost Update Problem
1ransaction A 1ime 1ransaction B
SELECT @x = e.x
FROM errand e
WHERE e.id = 1
t1
Disconnectfromdatabase.Locksarereleased,nomatterwhatwasthe
TransactionIsolationLevel(TIL).
BEGIN TRAN
t2
SELECT @x =
e.x
FROM errand e
WHERE e.id =
1
t3 "Change@x
t4
UPDATE errand
SET x = @x
WHERE id = 1
COMMIT TRAN
"Change@x t5
Connecttothedatabase.
UPDATE errand
SET x = @x
WHERE id = 1
t6
Note
Inaway,callingitTransactionAisamisnomerbecauseitisactuallytwotransactions.
Firsttheonethatreadsthevalueandthen,afterreconnect,thereisasecondtransaction
whenthevalueiswritten.
NotethatTransactionBmightlooknavetoyoubecausethesametaskcouldbedonewithcode,
suchasthatshowninListing9.14.Thatsaid,Idecidedtousethesameapproachasfor
TransactionAforreasonsofclarity.ItmightalsobethecasethatTransactionBmakesasimilar
disconnectasTransactionA,but aftertimet2.Anyway,themainproblemwouldbethesame,no
matterwhichapproachTransactionBuses.
Listing 9.14 A Better Approach for Transaction B
UPDATE errand
SET x = x + @something
WHERE id = 1
WastheLostUpdateproblempresentedinTable9.2aseriousoneornot?Itmightdependon
whatx is.Assumex issomekindofdescription.AllthathappensisthatTransactionAchangesthe
descriptionsothatasfarasTransactionAknows,thedescriptionvalueTransactionBgaveatt4
hasneverexisted. PerhapsTransactionA(orrathertheuserrequestingthetransaction)wouldn't
havewantedtomakethechangeifithadknownaboutthevalueatt5.
Ontheotherhand,ifx isanumericvaluethatisaccumulatedovertime,theLostUpdateproblem
isalwaysanastyone.Assumethatx is100att1.TransactionBadds20tothevalueatt3.x is
then120att4.TransactionAadds50tothevalueatt5,butTransactionAmakestheadditionto
100sox iswrittenas150att6.Itshould,ofcourse,be170instead,afterbothTransactionAand
Bhaveexecuted.
Note
InTable9.2,I'mnotusingstoredprocedures,butthisisonlytoprovideasconcisean
exampleaspossible.Nothingintheexampleisreallyaffectedbynotusingstored
procedures.IhavealsousedanINTEGER fortheIDcolumnintheexample,eventhough
youknowthatIusedUNIQUEIDENTIFIER before.IhaveusedanINT herebecause
UNIQUEIDENTIFIER occupiesseveralrowswithoutreallyaddinganyvaluableinformation
tothetable.
So,whatisthetypicalsolutiontotheLostUpdateproblem?Themosttypicalsolutionistoletthe
databasetakecareoftheproblembyusinglocks.IfweforgetaboutthefactthatTransactionA
disconnectsfromthedatabaseforamoment,itcouldmakeBEGIN TRAN attimet0andCOMMIT
TRAN aftertimet6.IfitusesTransactionIsolationLevel(TIL)REPEATABLE READ orSERIALIZABLE,
thesharedlockwillbetakenattimet1.ThatmakesitimpossibleforTransactionBtogetthe
exclusivelockthatitisaskingforattimet4.TransactionBisthusinawaitstate.Unfortunately,
TransactionAwon'tgetanexclusivelockeitherattimet6(atleastnotifTransactionBusesa
suitableTIL)becauseTransactionBhasasharedlockfromt2,soTransactionAalsostopsinawait
state.Thisisadeadlock,andhopefullySQLServerwilldecideonavictimsothatthistransaction
canbeabortedandtheothertransactioncanbefulfilled.Theabortedtransactioncanthenbe
restartedandthereisnolostupdate.
Thiswasquiteadrastic solutiontotheproblem.InChapter6,Isaidthatit'sbettertotakean
exclusivelockdirectlyifyouknowthatyouaregoingtoescalateasharedlocktoanexclusivelock
later.IfthatmethodologyisappliedtoTable9.2,itwouldmeanthatoptimizerhint(UPDLOCK)
wouldbeusedattimet1andtimet2sothattheSELECT statementwouldappearasitdoesin
Listing9.15.
Listing 9.15 SELECT Statement That Asks for an Exclusive Lock
SELECT @x = e.x
FROM errand e (UPDLOCK)
WHERE e.id = 1
TheresultofthiswouldbethatTransactionAisgrantedanexclusivelockattimet1.TransactionB
won'tbegrantedanexclusivelockatt2,butstopsinawaitstate.TransactionAcancontinueall
thewaytoaftertimet6(whereCOMMIT TRAN isexecuted);thelockisthenreleasedand
TransactionB'swaitstateisover.TransactionBgetsanexclusivelockandcanmoveon.
Sofarsogood,butthere'sjustoneproblem.Beforetheseexplanations,Isaidthatweshould
forgetthatTransactionAisdisconnectingfromthedatabase.Assumewecan'tforgetit.Assume
thatTransactionAneedsuserinteractioninsidethetransactionsoitknowshowtochangex in
timet5.Havingthatuserinteractionwouldbeaminordisasterforthroughputiftheexclusivelock
werekept.ImaginethatyouareTransactionB.Ifyou'relucky,youwouldhavetowaitforseconds,
andifyou'relesslucky,minutes.Meanwhile,theconnectionusedbyTransactionAisoccupied
duringtheuserinteraction,sothatresourcecan'tbeusedbyanotheruser.Thismethodology
wouldgreatlyharmscalability.
Dowehaveaproblemthat'simpossibletosolvehere?Ofcoursenot,butitrequiresanapproach
thatisalittledifferentthanwhatmostclassicdatabasetextbooksdiscuss.I'dliketodiscussthree
differentapproachestotheproblemand,whiledoingso,comeupwithaproposalforthe
recommendationIfindtobethebest.
Problem Solution 1: ROWVERSION Column
ProbablythemosttypicalsolutiontotheoptimisticconcurrencyproblemistouseaROWVERSION
columnonthetable.TheROWVERSION valueisreadintheSELECT (timet1intheexample inTable
9.2)andremembereduntilit'stimetosavethechanges.TheUPDATE statementinastored
proceduremightthenlookasisshowninListing9.16.
Note
InthecodeinListing9.16andmostoftenafterthat,Ihavejustaddedcommentsas
placeholdersfortheregulardeclarations,initializations,errorhandling,andExitHandler.
Listing 9.16 ROWVERSION Version of Concurrency Control
CREATE PROCEDURE Errand_Update (@id UNIQUEIDENTIFIER
, @x uddtX, @rowVersionOld ROWVERSION AS)
--The usual declarations and initializations.
UPDATE errand
SET x = @x
WHERE id = @id
AND rv = @rowVersionOld
--The usual error handler and ExitHandler.
InListing9.16,youcanseethattheoldROWVERSION value(foundattimet1)iscomparedtothe
currentROWVERSION valueattimet6.(TheROWVERSION columniscalledrv intheexample.)Ifthey
aredifferent,@@ROWCOUNT willgetthevalueof0.Asyouprobablyrememberfromthe
INSERT/UPDATE/DELETE exampleoferrorhandlingearlierinthischapter,theerror-handlingcode
considers@@ROWCOUNT = 0 anerror,sotheusercanbenotified.Theusercanthenrereadtherow
andmakethesamechangesagain,ifheorshestillthinksthechangesshouldbemade.Ofcourse,
youcanalsoimplementsothattheusercancompareearliervaluesandcurrentvaluestohisor
herownchangessothattheusercandecidewhatvaluesshouldbeusedofthosethreepossible
versions.
Note
ROWVERSION wascalledTIMESTAMP inversionsbeforeSQLServer2000.Ibelieve
ROWVERSION isabettername,becausethereisnotimeinthevalue,it'sjustanordinal.
Problem Solution 1: Pros and Cons
TheprosoftheROWVERSION solutionareasfollows:
N Itiscommonlyusedandwellknown.
N Itisinasenseautomaticandnotmuchextracodeisneeded.
N Itwill alsoworkwhenchangesaredonetothedatafromothercodeorbyhand,withoutthe
othercodehavingtoknowabouttheprotocol.
Andtheconsofthissolutionareasfollows:
N Sometimesitistoocoarsegranular.Assumeyouwanttochangethecolumncalled
description andanotheruserhaschangedthecolumncalledresponsible.Shouldthat
reallystopyourUPDATE?Probablynot,butthatisexactlywhatmighthappenwhen
ROWVERSION isusedforconcurrencycontrol,becausetherecanonlybeoneROWVERSION on
atable.Thisisparticularlyaproblemforwidetables,orrathertablesthatareusedfor
severaldifferentpurposes.
N Youcan'tuseDistributedPartitionedViews(DPVs)fortableswithaROWVERSION column.
ThisisbecausetheROWVERSION seriesislocaltoeachmachine.WithDPV,thetablewillbe
partitionedoverseveralmachines.
N Youcan'tusemergereplicationfortableswithROWVERSION columns.Well,youcanifyou
saythatthosecolumnsshouldn'tbereplicated,butthenyourmechanismforconcurrency
controldoesn'treallywork.Ontheotherhand,concurrencycontrolinreplicationscenarios
isacompletelydifferentstoryandnotwithinthescopeofthisbook.
Problem Solution 2: Before and After Values
Inthesecondproblemsolution,weusethevaluesfromtheSELECT attimet1andcheckthatthe
valuesarestillthesameattimet6beforeyoumaketheupdate.AtypicalsolutiontoanUPDATE
storedprocedurewouldbetousecodeshowninListing9.17.
Listing 9.17 Before and After Values Version of Concurrency Control: Example 1
CREATE PROCEDURE Errand_Update (@id UNIQUEIDENTIFIER
, @xNew uddtX, @xOld uddtX) AS
--The usual declarations and initializations.
DECLARE @theCurrentX uddtX
SELECT @theCurrentX = e.x
FROM errand e (UPDLOCK)
WHERE e.id = @id
--The usual error handler.
IF @theCurrentX = @xOld BEGIN
UPDATE errand
SET x = @xNew
WHERE id = @id
--The usual error handler.
END
ELSE BEGIN
SET @anError = 80004
SET @anErrorMessage = 'Conflict in update.'
GOTO ExitHandler
END
--The usual ExitHandler.
Note
It'simportanttounderstandthatthecodeinListing9.17mustexecuteinanactive
transactiontoworkcorrectly.
TheimplementationshowninListing9.17isabitclumsy,especiallyifyouimaginethatyouhave
50beforeandaftervalueswithwhichtowork.Iearliercomplainedabouttheimplementationof
UpdateBatch() inADO,butthroughitIlearnedanotherwayofdoingthis,whichIthinkisamuch
bettersolution.InListing9.18,youcanseethatI usetheoldcolumnsintheWHERE clauseinstead.
If@@ROWCOUNT = 0,Iknowthatsomeofthevalueshavechanged.
Listing 9.18 Before and After Values Version of Concurrency Control: Example 2
CREATE PROCEDURE Errand_Update (@id UNIQUEIDENTIFIER
, @xNew uddtX, @xOld uddtX) AS
--The usual declarations and initializations.
UPDATE errand
SET x = @xNew
WHERE id = @id
AND x = @xOld
--The usual error handler and ExitHandler.
Problem Solution 2: Pros and Cons
Theprosofthebeforeandaftervaluessolutionareasfollows:
N Itisasgranularasyouwant.Youcanhavecolumn-levelconcurrencycontrolinsteadof
row-levelconcurrencycontrol,althoughIdon'trecommendthis.(Row-levelconcurrency
controliswhatyoualwaysgetwhenROWVERSION isused.)
N Assumingyouwanttoauditchangesbywritingbeforeandaftervaluestoanaudittablein
yourstoredprocedure,youdon'thavetoreadfromthetabletocollectthebeforevalues,
becausetheyareprovidedasparameterstothestoredprocedure.
N Itisnotimperativethattheconsumerrereadtherowbeforeupdatingitdirectlyagain.This
dependsonwhetherthestoredprocedurethathandlestheupdatechangesanyofthe
columns.Ifitdoesn't,theconsumerhasalltherequiredinformation.Heorshecansimpl y
movethenewlysavedvaluestothevariablesholdingtheoldvalues.
N Thesolutionwillworkwhenchangesaremadetothedatafromothercodeorbyhand,
withouttheothercodeneedingtoknowabouttheprotocol.
Note
Inaddition,thedisadvantageofhandlingthebusinessruletocheckthatastatuschange
isvaliddisappears.BusinessrulesarediscussedinChapter7,"BusinessRules.
Theconsofthissolutionareasfollows:
N Thereismoredatatokeeptrackof,moredatatokeepattheconsumer,moreparameters
tosetbeforecallingthestoredprocedure,moredatatosendtothestoredprocedureatthe
timeofUPDATE,andmoredatatouseintheWHERE clause.Thismaynotseemtobeabig
problematfirst,butwhenyouhave50columnstoupdateinonestoredprocedure,youwill
have100+columnsintheparameterlisttothestoredprocedure.Furthermore,evenifyou
haveonlyupdatedonecolumnofthese50,youstillhavetocompare50differentcolumns
intheWHERE clause.
N Theremightbeproblemswithprecisionwhenyoutossfloatnumbersoverto.NET
componentsandbacktothestoredproceduretobeusedintheWHERE clause.
Problem Solution 3: Custom Columns
Thethirdsolutiontotheconcurrencyproblemiscompletelymanual.It'sbestillustratedbyan
example,whichcanbeseeninListing9.19.HereyoucanseethatIkeepaSMALLINT column
calledrv (justaswiththeROWVERSION columninthefirstsolution)inthetable.Whentherowwas
readfromthedatabaseattimet1,therv wasfetchedtoo.Whenit'stimetoUPDATE therowat
timet6(asisshowninListing9.19),thecodechecksthattherv valueisthesameasitwaswhen
itwasread.Ifitis,therv valuewillalsobeincreased.
Listing 9.19 Custom Value Version of Concurrency Control
CREATE PROCEDURE Errand_Update (@id UNIQUEIDENTIFIER
, @x uddtX, @rvOld SMALLINT) AS
--The usual declarations and initializations.
UPDATE errand
SET x = @x
, rv = CASE WHEN rv < 32767 THEN rv + 1 ELSE 1 END
WHERE id = @id
AND rv = @rvOld
--The usual error handler and ExitHandler.
Notethattherv columninListing9.19isagoodexampleofacolumnthatshoulduseauser-
defineddatatype.Ontheotherhand,youwillhavetochangethebordervalueinthecode(see
32767 inListing9.19)ifyouchangethedatatypefortheuser-defineddatatype.Imissglobal
constantsinT-SQLverymuch.Orevenbetter,Iwishwehadavailableapossibilitysimilartothe
onein.NETasisshowninListing9.20.
AlsoworthmentioningisthatIcouldusenegativevaluestoo,anoften-forgottentechniquein
situationslikethis.Thatdoublesthesizeofthespanfortheintervalofvalues.
Listing 9.20 How to Ask for the Max Size of a Variable in Visual Basic .NET
Dim anInteger As Integer
Console.WriteLine(anInteger.MaxValue.ToString())
It'snosecretthatT-SQLisn'tatallaspowerfulasthe.NETlanguages,butitstillfrustratesme.
Problem Solution 3: Pros and Cons
Theprosofthecustomcolumnssolutionareasfollows:
N Ithasfullflexibility.Becauseyouprogramthissolutiononyourown,youhaveacertain
amountoffreedom.
N Youcan"partitiontherowinasmanyconcurrencycontrolpiecesyouwantbyusing
severalrv columns.
N Youcanactuallyseehowmanytimesarowhasbeenchanged,whichmightbeusefulin
somesituations.(Ifso,youshouldperhapsuseINT insteadofSMALLINT asIdidinListing
9.19.)
N Youarenotobligedtorereadtherowbeforeupdatingitdirectlyagain.Thisdependson
whetherthestoredprocedurehandlingtheUPDATE changesanyofthecolumns.Ifitdoes
not,theconsumerhasallthenecessaryinformation.Theconsumercanjustmovethe
newlysavedvaluestothevariablesholdingtheoldvalues,andthecurrentrv istheoldrv
+1.
Theconsofthissolutionareasfollows:
N It'scompletelymanual,soyouhavetoprogramthissolutioncompletelyonyourown.(As
yousawintheprevious"prossection,Ilistedthisasanadvantagealso.Whetherit'san
advantageordisadvantagedependsonthesituation.)
N Youmustwatchoutforborders.Nomatterwhatdatatypeyouusefortherv column,there
isavaluebordertowatchoutfor.
N Theprotocoldoesn'tworkifotherUPDATE requestsdon'tfollowtheprotocol.However,ifall
UPDATEsaredonethroughastoredprocedure,thisisnotaproblem.
What If I Don't Like Stored Procedures?
Asyouknowbynow,I'mveryfondofstoredprocedures,andI didthe
checkinginastoredprocedureforallthreesolutions.However,each
solutioncan,ofcourse,beusedfromthe.NETcomponentsandwith
dynamicSQLinstead.However,Ifindthistaskisveryclosetothe
database.Also,watchoutforoverheadifyouuseseveralroundtripswith
thethirdsolutionbecausethethroughputwillbedamaged.
Problem Solution 4: Taking Care of the Concurrency Control in a .NET Component
Allthreesolutionsdiscussedsofarhavebeenlocatedinstoredprocedures.Eachofthe
mechanismsofthosesolutionscanbeusedforsolvingtheconcurrencycontrolproblemin.NET
componentsinsteadofasinstoredprocedures.Idon'tthinkthisisagoodideathough,because
youwillgetlongertransactionsandlongerlocks.Intheevaluationlaterinthischapter,this
approachwillbediscussedsomemore.
My Proposal for Approaching Optimistic Concurrency Control of Disconnected Data
Asalways,comingupwitharecommendationisverydifficult,becauseitdependssomuchonthe
situationathand.Inthecaseofthedifferenttechniquesforoptimisticconcurrencycontrolof
disconnecteddata,thisiscertainlythecase.Allthreesolutionsworkjustfine,andworkbestin
differentsituations.Asusual,thebestapproachistousethebestsolutionforthesituation.
Ifyoufindthatmyrecommendationinthepreviousparagraphwaskindofanonrecommendation,
IcansaythatifIhadtochooseonlyonesolutiontouseallthetime,Iprefersolution3,the
customcolumnssolution,becauseitisthemostflexible.Justonewordofcautionthough-asyou
know,flexibilityoftenmeansmorework.
Pessimistic Concurrency Control
Wehavenowdiscussedseveralsolutionsforhowtogetoptimisticconcurrencycontrolwhenthe
datahasbeendisconnectedfromthedatabase.Incertainsituations,youneedpessimistic
concurrencycontrolinstead.AssumethatyouarethinkingofbuyingtwoflightticketstoSweden
foryouandyourlovedone.(What'sthat?You'drathergotoFrance?Trustme,youshouldvisit
Sweden..)So,youasktheairlineifanyticketsareleftandtheytellyouthatyouareinluck,they
havejusttwo.Thesalesclerkasksyouifyouwantthem;youthinkfor10secondsandsayyoudo.
Rightatthatmoment,youthinkthoseticketsareyours.However,ifthesalesclerkhadbeenusing
anapplicationthatusedoptimisticconcurrencycontrol,anyonecouldhaveboughttheticketswhile
youweremakingupyourmind.
AsimilarexamplecanbetakenfromoursampleapplicationAcmeHelpDesk.Whenaproblem
solverinspectsareportederrand,heexpectsthatnobodyelsewilltakeontheresponsibilityof
solvingthaterranduntilhehasdecidedifheisasuitableproblemsolverhimself.Althoughit
involvesmorework,itispossibletoimplement pessimisticconcurrencycontrolfordisconnected
scenarios.Let'stakealookatmyrecommendedsolutionforaddingpessimisticconcurrencycontrol
fordisconnectedscenariostoyourapplications.
Note
Onceagain,rememberthatIwantthedatatobedisconnectedfromthedatabaseand
theconnectiontobereleasedduringthetimetheuseristhinking.Otherwise,aneasy
solutionwouldbetonotdisconnectfromthedatabaseandkeepanexclusivelockonthe
rowsinthedatabaseforthoseticketsortheerrand.Asamatteroffact,thisisn'tagood
solutionanyway,becauseyoudon'twantausertodecidethelengthintimeforthelocks.
Thiscan'tbesaidoftenenough.KeepinmindtoothatwhentheConsumertierisa
classicWebapplication,it'salsoaproblemkeepingaconnectionopenoverseveral
requests.
Problem Solution: My Proposal for Approaching Pessimistic Concurrency Control of
Disconnected Data: Custom Lock Table
Let'scontinuewiththeerrandsamplewhereIshowasolutionproposaltotheprobl em.Aneasy
solutionforachievingpessimisticconcurrencycontrolistoaddalock table,suchastheoneshown
inFigure9.2.Iftheid ofthelock tableisofdatatypeUNIQUEIDENTIFIER,thesametablecanbe
used,evenifseveraltables(allmusthaveUNIQUEIDENTIFIERs,ofcourse)needtousethiscustom
solutionforpessimisticconcurrencycontrol.Ifyoudon'tuseUNIQUEIDENTIFIERsinyourtablesbut
perhapsuseINT foryourprimarykeys,youmusthaveonelock tablepertablethatcanbe
handledthewayI'mabouttoshow.Ofcourse,youcouldalsohaveacompositeprimarykeyofid
andtablenameinthelock tableandthenyouonlyneedonelocktable.Inanycase,thisisa
situationwhenUNIQUEIDENTIFIER isverygood.
Figure 9.2. Lock table for custom locks.
Ifallyourreadingoferrandsgoesthroughastoredprocedure(whichIrecommendanyway),you
canusecodelikethatshowninListing9.21.
Listing 9.21 Use of Lock_GrantRead()
EXEC @aReturnValue = Lock_GrantRead@id
, @userId
SELECT @anError = @@ERROR
IF @anError <> 0 OR @aReturnValue <> 0 BEGIN
SET @anErrorMessage = 'Problem when granting read.'
IF @anError = 0 BEGIN
SET @anError = @aReturnValue
END
GOTO ExitHandler
END
SELECT e.id, e.description ,...
FROM errand e
WHERE e.id = @id
--The usual error handler.
Iftheusertriestoreadanerrandfromthedatabasethatisn'tlocked,heorshewillreceive0 as
thereturnvaluefromtheLock_GrantRead() storedprocedure.TheLock_GrantRead() stored
procedurewillalsoINSERT arowtothelock tablewiththe@id,the@userId,andthetimefor
whenthelockwasacquired.Because0 wasreturned,theuserwillgettheinformationbackabout
theerrand.
Ontheotherhand,iftheerrandwasalreadytobefoundinthelock table,andthelockstillisvalid
andownedbysomebodyelse,Lock_GrantRead() willreturntheerrorcodeassociatedwiththe
problemthatacustomlockcan'tbegranted,andtheSELECT willnottakeplace.Ifthereisalready
arowinthelock tablefortheid butthelockismorethan10minutesold(ifthatisthevalidity
timeforthisexample),theuserwillbegrantedthelockand0 willbereturned.
Ithinkit'simportanttosetatimewhenthelockisnolongervalid;otherwisesooner,ratherthan
later,youwillgetintothesituationwhereanadministratorhastochangealockmanually.
TheinnerworkingsoftheLock_GrantRead() storedprocedurelooksasshowninListing9.22.
Listing 9.22 Implementation of Lock_GrantRead()
CREATE PROCEDURE Lock_GrantRead (@id UNIQUEIDENTIFIER
, @userId uddtUserId) AS
--The usual declarations and initializations.
DECLARE @aCurrentUserId uddtUserId
, @aLockedDatetime DATETIME
SET @theTranCountAtEntry = @@TRANCOUNT
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
IF @theTrancountAtEntry = 0 BEGIN
BEGIN TRAN
END
SELECT @aCurrentUserId = userid
, @aLockedDatetime = lockeddatetime
FROM lock (UPDLOCK)
WHERE id = @id
SET @anError = @@ERROR
IF @anError <> 0 BEGIN
SET @anErrorMessage = 'Problem with SELECT from lock table.'
GOTO ExitHandler
END
IF @aCurrentUserId IS NULL
OR @aCurrentUserId = @userId
OR DATEADD(n, dbo.LockValidNMinutes(), @aLockedDatetime)
< GETDATE() BEGIN
DELETE FROM lock
WHERE id = @id
SET @anError = @@ERROR
IF @anError <> 0 BEGIN
SET @anErrorMessage = 'Problem with DELETE from lock table.'
GOTO ExitHandler
END
INSERT INTO lock
(id, userid, lockeddatetime)
VALUES
(@id, @userId, GETDATE())
SELECT @anError = @@ERROR, @aRowcount = @@ROWCOUNT
IF @anError <> 0 OR @aRowcount = 0 BEGIN
SET @anErrorMessage = 'Problem with INSERT to lock table.'
IF @anError = 0 BEGIN
SET @anError = 80001
END
GOTO ExitHandler
END
END
ELSE BEGIN
SET @anError = 80004
SET @anErrorMessage = 'Read lock can not be granted.'
END
--The usual ExitHandler.
Asyoucansee,thelockisgrantedinoneof thefollowingcircumstances:
N Whenthereisnolockfortherow
N WhenIhavethecurrentlockfortherow
N Whenthereisalockfortherow,butitistooold
ItisalsoworthmentioningthatintheimplementationofLock_GrantRead(),IuseascalarUDF
calledLockValidNMinutes() forprovidingtheconstantofhowmanyminutesthelockisvalid.
InListing9.22,thestoredprocedureforgrantingaread(andtakingalock)wasshown,butyou
alsoneedasimilarstoredproceduretobeusedbeforeyouupdatetherow.Thesamerulesapply
asinListing9.22,buttherowwillbeunlockedinsteadoflocked.Myimplementationofthatstored
procedureiscalledLock_GrantUpdate() andcanbedownloadedfromthebook'sWebsiteat
www.samspublishing.com.
Youalsoneedana_Lock_Release() procedure.Asyousaw,Iprefixa_Lock_Release() witha_
andthatusuallymeansthatthisstoredprocedurecanbecalledfromthePersistentAccesslayer.
Thatisexactlythereasonthistimetoo.Lockswillbereleasedwiththehelpof
Lock_GrantUpdate() thatiscalledbytheUPDATE storedprocedure.Butwhatiftheuserthatis
grantedthelockdecidesnottoUPDATE theerrand?Thenthelockisleftinthedatabaseuntilits
validitytimeisatanend.
Whatiftheuserdoesn'tclicktheClosebuttonoftheformbutjustterminatestheapplication?This
isaparticularproblemwithWebapplicationsbecauseyouhaveverylittlecontrolofhowtheuser
usestheapplication.Therewill alwaysbesituationswhenalockisn'treleased,butthatisexactly
theproblemthatthetimevaliditywillsolvegracefully.
Weak Spots in the Proposed Solution
Althoughlaterinthischapterwewillevaluatethisproposalagainstthecriteriaweestabl ishedin
Chapter2,"FactorstoConsiderinChoosingaSolutiontoaProblem,let'slookatafewofthe
specificweakspotswithhavingacustomlock table.Thefirst,andprobablythemostimportant
one,isthatitconsumesalotofresources,sodon'toverusethissolution.Youmightfindyourself
usingthesolutionforeachandeverytable.Ifso,thisisasignofapossibledesignerrororan
incorrectperceptionofcustomerrequirements.Isitreallyimportanttoonlyletoneuseratatime
updatetheinformationaboutaspecificcustomer,forexample?Whycan'titbetreatedwith
optimisticconcurrencycontrolofdisconnecteddatainstead?Themoralofthestoryistopush
optimisticconcurrencyofdisconnecteddataandonlyusepessimisticconcurrencycontrolof
disconnecteddatawhenit'sabsolutelynecessary.Thinkaboutpessimisticconcurrencycontrolof
disconnecteddataasreservingsomething.Youdon'tusethereservationmechanismallthetime.
Youwouldalsothinkthatit'sallrighttofirsttakealookattheitemandthenexplicitlyreserveit.
WhatImeanis,lettheusershowhisorherintenttoupdateandatthatpointgrabalock.
Evenifyouhaveacorrectimplementationoftheproposedsolutionforpessimisticconcurrency
controlofdisconnecteddata,itdoesn'thaveexactlythesamesemanticsasthepessimisticlocking
handlednativelybythedatabase.Thereasonisthatthelockwillonlybevalidforn minutes.Ifyou
thinkfortoolong,thelockisgonewhenyouthinkthatyoustillhaveit.Youcouldimplementa
mechanismintheconsumer,givingawarningtotheuserwhenthelockwillsoonbecomeinvalid.
Inaddition,afteryouhaveupdatedtherow,youhavetoreadtherowagainifyouwanttokeep
thelock.Thisisnotahugeproblem,butitaddstotheoverhead.
It'snotalwaysenoughonlytohavelockingforauserId.Ifso,theusermightkickuptwodifferent
browsersandtakealookatthesameerrandinbothofthem.Thisisperfectlyvalid,becauseboth
browsersareusedbythesameuserId.Unfortunately,whenheorsheclickstheClosebuttonin
oneofthebrowsers,thelockisreleasedforbothbrowsers,withoutthemknowing.Youcanadd
sessionId asanotherpieceofinformationforthelock,andtheneachbrowserwillbetreatedas
twodifferentrequesters.Ifonebrowserhasalock,theotheronewon'tbegrantedthatlock,even
iftheuserId isthesame.AslightlyimprovedprotocolwouldbetoonlyletthesessionId that
holdsthelockreleaseitandusethelockforupdating.Still,thesameuserId isalwaysgranted
rightstoreadtherow.
Note
ThereareseveralwaystocreateasessionId,butacommononeistogenerateaGUID
andstoreitasacookieforWebapplications,forexample.AGUIDworksperfectlywellin
thecaseofWindowsFormsconsumerstoo.
Someusersaren'tauthorizedtochangeaspecificerrand,inwhichcasethereisnopointin letting
themgrabalockeither.Thesamegoesforcallstoa_Lock_Release().It'snousecallingthat
storedprocedureiftheuserlooksatanerrandthatheorsheisn'tallowedtoupdate.Ifyoubuild
logiclikethisinyourapplication,youwilldecreasetheoverheadofthesolution.Finally,another
waytodecreasetheoverheadistotakecareofthepessimisticlockingatacoarsegranularlevel.
Don'tusethecustomlockingonseparateactions,butonlyonthecompleteerrand.
Evaluation of Proposals
It'snowtimetoevaluatetheproposalsIpresentedinthischapteragainstthecriteriawe
establishedinChapter2.We'llfirstevaluatethecompleteproposalforhowtocatcherrorsinboth
storedproceduresand.NETcomponents,andthenwe'lllookattheoptimisticconcurrencycontrol
proposal.Finally,we'lllookatthepessimisticconcurrencycontrolproposalIjustpresented.
Evaluation of Proposal for Handling Errors
Theproposalforhowtodealwitherrorsisactuallyquitealargeone.First,acompleteprotocol
mustbefulfilledinthestoredproceduresandthen,inthe.NETcomponents,acentralizedmethod
willbeusedfor"raisingexceptions.Thetypeofconsumerfactorisonlyofinterestinthecaseof
.NETexceptions,becausetheonlyconsumertothestoredproceduresshouldbethePersistent
Accesslayer.Evenso,thetypeofconsumershouldn'taffectthe.NETexceptionsatall.The
Applicationlayerwillraiseanexceptionand,iftheconsumerwantstoseeexceptionsanotherway,
theConsumerHelperlayerwilltakecareofthat.
Asyoumayguess,performanceandscalabilitywillbeaffectedwhenerrorhandlingisused,but
then,dowereallyhaveachoice?Weneedtohandleerrorsandthecostisn'tallthatgreat.Wecan
usedifferentstrategies,butthereisn'tusuallymuchoverheadwiththesolutionIuse.Inthecase
ofabnormalsituations,thereisalotofoverhead,butexceptionsshouldhappensorarelythatthis
isnotreallyaproblem.
Asyoumightguess,Ifindthesolutionbothmaintainableandreliable.It'smaintainablebecause
it'sadistinctmodelforhowtotreaterrorhandling.Consistentcodemeansmaintainablecode.One
exampleofhowreliabilityisincreasedistheparanoiderrorhandlingusedinthestoredprocedures.
AsImentionedinthechapter,Ievenhandleerrorsduetomissingstoredprocedures.
Idiscussedtheerror-loggingsolutionindetailinChapter4.Thankstothiserrorlogging,
debuggabilityisgreatlyincreased.Ithinkthereusesituationisgoodforboththestored
proceduresandthe.NETcomponents.BecausethestoredproceduresusebothRETURN() and
RAISERROR(),itwillbepossibleforthemtobereusedbymostotherstoredprocedures,andvice
versa.ThemainproblemasIseeitwithreusingstoredproceduresandcomponentsfromother
sourcesisthatIexpectXML-formattederrordescriptionsfromRAISERROR() fortheerrorlogging
solutiontoworkjustasexpected.Ialsowantraisedexceptionsthatarelowerthantheentrypoint
touseinnerexceptionssoasnottolose theerrorstack.Writingacustomexceptionschemafor
the.NETcomponentscouldmeanthatexactlythesameschemacouldbeusedforCOM
components,sotheycouldbothbebetterintegratedinsteadofrelyingoninterop.Still,inthis
case,Idefinitelyprefertoplaywiththecardswehave.Finally,asIsaidearlier,consistentcode
meansmaintainablecode,andthankstothis,productivitygainstoo.
Evaluation of Proposal for Approaching Optimistic Concurrency Control of
Disconnected Data
Inthischapter,Iactuallylistedtwodifferentproposalsforapproachingoptimisticconcurrency
controlofdisconnecteddata.Onewastousethebesttechniqueforthesituationathand,andthe
otherwastousethecustomcolumnapproachbecauseithaslessconstraints.Inthisevaluation,I
willlookattheconceptualproposalfordealingwithoptimisticconcurrencycontrolinstored
proceduresingeneral,andalsothecustomapproachIrecommend.Inthisevaluation,youwillfind
thattherewillbequiteafewfactorsthataren'taffectedatall.Let'sstartwithperformanceand
scalability.
Performanceandscalabilityfareverywellthankstotheuseofstoredproceduresforthesolution,
eventhoughyetanothercolumnhastobechangedateveryUPDATE.However,thisdoesn'taffect
theperformanceandscalabilitymuchbecausetherowhastobereadandlockedanyway.Atthe
book'sWebsiteatwww.samspublishing.com,youcanseeadiagramcomparingthedifferencesin
throughputbetweenthethreeproposals,andalsothefourthsolutionfortakingcareofthecontrol
ina.NETcomponent.(Pleaserememberthattheresultswillbeaffectedbyseveralfactorsandare
onlycorrectintheactualsituationusedforthetest.)
Maintainabilityisbetterthanusingbefore-aftervalues,becausethecontrolsolutionisn'taffected
atallwhenanadditionalcolumnisadded.Therearealsofewerparameterstosendtostored
procedures.Reliabilityisjustfine,butaproblemoccurswhenthetablesarewrittentobysources
thatdon'tfollowtheprotocol.Itisexactlythesameforreusability,ofcourse.Productivityisfine
too,especiallycomparedtowhenbefore-aftervaluesareused.
Evaluation of Proposal for Approaching Pessimistic Concurrency Control of
Disconnected Data: Custom Lock Table
Evenhere,inthecaseofconcurrencycontrol,Idecidedtohandleitinmystoredprocedures.
WhenIcometothefactoroffarm-/cluster-enabling,youwillseethatthishasadvantages,but
let'sstartfromthebeginning.
Performanceandscalabilitywilldefinitelybenegativelyaffectedwhenyouusetheproposed
solution.Don'toveruseit-Irepeat,don'toveruseit!Togiveyouapictureoftheoverhead,Ihave
comparedtherecommendedsolutionforpessimisticconcurrencycontrolofdisconnecteddatawith
therecommendedsolutionofitsoptimisticsibling.Youcanseetheresultatthebook'sWebsiteat
www.samspublishing.com.
Becauseallthelockingworkisdoneinstoredprocedures,itisrelativelyencapsulatedand
thereforemaintainable.Reliabilitymaybeaproblem,becauseifnotallrequestingpartiesfollow
theprotocol,itfails.Itisalsothecasethataftern minutes,yourlockisgone,anditmaynotbe
possibletosavethechangesl o c k = q ' M K M M N = q = q M K V U ' M K V R V S O = M = q ' M K M U a = q ' M = M K S Q = Q T K V S = q ' M = ' M K M M Q = q ' x E B e F T E c F z q g ' b q ' n ' ' N = S V = M = q ' J M K M M O V = q ' Y Q U M P [ q ' b q ' n ' ' N = N K M U M N = S N N K M Q = N M M S K V N V V = ' t = ' _ S U R = ' _ ' V K V S = M = M = M V = q ' Y N N M P [ q ' M K T ue p - # _ * ] M o l nd@ & %* - /-F)!M ieo - u m - * - * = * m mae omMt t dand
willcertainlyenterthemarketinthecomingmonths;therefore,youcanfindanup-to-date
referencelistatthisbook'sWebsiteatwww.samspublishing.com.
Appleman,D.Moving to VB.NET: Strategies, Concepts and Code.Apress;2001.ISBN1-893115-
97-6
Beck,K.Extreme Programming Explained: Embrace Change.Addison-Wesley;1999.ISBN0-201-
61641-6
Ben-Gan,I.andT.Moreau.Advanced Transact-SQL for SQL Server 2000.Apress;2000.ISBN1-
893115-82-8
Bernstein,P.A.andE.Newcomer.Principles of Transaction Processing.MorganKaufmann;1996.
ISBN1558604154
Booch,G.,Rumbaugh,J.,andI.Jacobson.The Unified Modeling Language User Guide.Addison-
Wesley;1998.ISBN0-201-57168-4
Brill,G.Applying COM+.NewRiders;2001.ISBN0-7357-0978-5
Brown,R.,Baron,W.,andW.D.ChadwickIII.Designing Solutions with COM+ Technologies.
MicrosoftPress;2001.ISBN0-73561127-0
Buschmann,F.,Meunier,R.,Rohnert,H.,Sommerlad,P.,andM.Stal.Pattern-Oriented Software
Architecture: A System of Patterns.Wiley;1996.ISBN0-471-95869-7
Celko,J.SQL for Smarties, Second Edition.MorganKaufmann;1999.ISBN1-55860-576-2
Connolly,T.andC.Begg.Database Systems: A Practical Approach to Design, Implementation and
Management, Second Edition.Addison-Wesley;1998.ISBN0-201-34287-1
Date,C.J.An Introduction to Database Systems, Seventh Edition.Addison-Wesley;2000.ISBN0-
201-38590-2
Delaney,K.Inside SQL Server 2000.MicrosoftPress;2000.ISBN0-7356-0998-5
Ewald,T.Transactional COM+: Building Scalable Applications.Addison-Wesley;2001.ISBN0-201-
61594-0
FlemingC.C.andB.vonHalle.Handbook of Relational Database Design.Addison-Wesley;1989.
ISBN0-201-11434-8
Fowler,M.Analysis Patterns.Addison-Wesley;1997.ISBN0-201-89542-0
Franklin,K.VB.NET for Developers.SamsPublishing;2001.ISBN0-672-32089-4
Gamma,E.,Helm,R.,Johnson,R.,andJ.Vlissides.Design Patterns.Addison-Wesley;1994.ISBN
0-201-63361-2
Gunnerson,E.A Programmer's Introduction to C#, Second Edition.Apress;2001.ISBN1-893115-
62-3
Gray,J.andA.Reuter.Transaction Processing: Concepts and Techniques.MorganKaufmann;
1993.ISBN1-55860-190-2
Heinckiens,P.M.Building Scalable Database Applications.Addison-Wesley;1997. ISBN0-201-
31013-9
Henderson,K.The Guru's Guide to Transact-SQL.Addison-Wesley;2000.ISBN0-201-61576-2
Hunt,A.andD.Thomas.The Pragmatic Programmer.Addison-Wesley;1999.ISBN0-201-61622-X
KernighanB.W.andR.Pike.The Practice of Programming.Addison-Wesley;1999.ISBN0-201-
61586-X
Kline,K.,Gould,L.,andA.Zanevsky.Transact-SQL Programming.O'Reilly;1999.ISBN1-56592-
401-0
Lowy,Juval.COM and .NET Component Services.O'Reilly;2001.ISBN0596001037
Meyer,B.Object-Oriented Software Construction, Second Edition.PrenticeHall;1997.ISBN0-13-
629155-4
Pattison,T.Programming Distributed Applications with COM+ and Visual Basic 6.0, Second Edition.
MicrosoftPress;2000.ISBN0-7356-1010-X
Platt,D.S.Introducing Microsoft .NET.MicrosoftPress;2001.ISBN073561377X
Pfister,G.F.In Search of Clusters, Second Edition.PrenticeHall;1998.ISBN0-13-899709-8
Robbins,J.Debugging Applications.MicrosoftPress;2000.ISBN0-7356-0886-5
Schmidt,D.,Stal,M.,Rohnert,H.andF.BuschmannPattern-Oriented Software Architecture:
Volume 2: Patterns for Concurrent and Networked Objects.Wiley;2000.ISBN0-471-60695-2
Schulmeyer,G.G.andJ.I.McManus.Handbook of Software Quality Assurance, Third Edition.
PrenticeHall;1998.ISBN0-13-010470-1
Sommerville,I.Software Engineering, Sixth Edition.Addison-Wesley;2001>.ISBN0-201-39815-X
Stonebraker,M.andP.Brown.Object-Relational DBMSs: Tracking the Next Great Wave, Second
Edition.MorganKaufmann;1999.ISBN1-55860-452-9
Sundblad,S.andP.Sundblad.Designing for Scalability with Windows DNA.MicrosoftPress;2000.
ISBN0-7356-0968-3
Sunderic,D.andT.Woodhead.SQL Server 2000 Stored Procedure Programming.Osborne;2000.
ISBN0-07-212566-7
Szyperski,C.Component Software: Beyond Object-Oriented Programming.Addison-Wesley;1997.
ISBN0-201-17888-5

You might also like