344 106 3MB
English Pages 573 Year 2006
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET, 1/e By Jimmy Nilsson ............................................... Publisher: Addison W e sle y Pr ofe ssion a l Pub Dat e: M a y 0 8 , 2 0 0 6 Print I SBN- 10: 0 - 3 2 1 - 2 6 8 2 0 - 2 Print I SBN- 13: 9 7 8 - 0 - 3 2 1 - 2 6 8 2 0 - 4 Pages: 5 7 6
Table of Cont ent s | I ndex
" [ This] is a book about design in t he .NET world, driven in an agile m anner and infused wit h t he product s of t he ent erprise pat t erns com m unit y. [ I t ] shows you how t o begin applying such t hings as TDD, obj ect relat ional m apping, and DDD t o .NET proj ect s...t echniques t hat m any developers t hink are t he key t o fut ure soft ware developm ent .... As t he t echnology get s m ore capable and sophist icat ed, it becom es m ore im port ant t o underst and how t o use it well. This book is a valuable st ep t oward advancing t hat underst anding." Mart in Fowler, aut hor of Refact or ing and Pat t erns of Ent erprise Applicat ion Archit ect ure
Pat t erns, Dom ain- Driven Design ( DDD) , and Test - Driven Developm ent ( TDD) enable archit ect s and developers t o creat e syst em s t hat are powerful, robust , and m aint ainable. Now, t here's a com prehensive, pract ical guide t o leveraging all t hese t echniques prim arily in Microsoft .NET environm ent s, but t he discussions are j ust as useful for Java developers. Drawing on sem inal work by Mart in Fowler ( Pat t erns of Ent erprise Applicat ion Archit ect ure) and Eric Evans ( Dom ain- Driven Design ) , Jim m y Nilsson shows how t o creat e real- world archit ect ures for any .NET applicat ion. Nilsson illum inat es each principle wit h clear, well- annot at ed code exam ples based on C# 1.1 and 2.0. His exam ples and discussions will be valuable bot h t o C# developers and t hose working wit h ot her .NET languages and any dat abaseseven wit h ot her plat form s, such as J2EE. Coverage includes ·
Quick prim ers on pat t erns, TDD, and refact oring
·
Using archit ect ural t echniques t o im prove soft ware qualit y
·
Using dom ain m odels t o support business rules and validat ion
·
Applying ent erprise pat t erns t o provide persist ence support via NHibernat e
·
Planning effect ively for t he present at ion layer and UI t est ing
·
Designing for Dependency I nj ect ion, Aspect Orient at ion, and ot her new paradigm s
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET, 1/e By Jimmy Nilsson ............................................... Publisher: Addison W e sle y Pr ofe ssion a l Pub Dat e: M a y 0 8 , 2 0 0 6 Print I SBN- 10: 0 - 3 2 1 - 2 6 8 2 0 - 2 Print I SBN- 13: 9 7 8 - 0 - 3 2 1 - 2 6 8 2 0 - 4 Pages: 5 7 6
Table of Cont ent s | I ndex
Copyright Praise for Applying Domain-Driven Design and Patterns About the Author Forewords Preface: Bridging Gaps Acknowledgments Part I: Background Chapter 1. Values to Value: Or Embarrassing Ramblings When Self-Reflecting on the Last Few Years Overall Values Architecture Styles to Value Process Ingredients to Value Continuous Integration Don't Forget About Operations Summary Chapter 2. A Head Start on Patterns A Little Bit About Patterns Design Patterns Architectural Patterns Design Patterns for Specific Types of Applications Domain Patterns Summary Chapter 3. TDD and Refactoring Test-Driven Development (TDD) Mocks and Stubs Refactoring Summary Part II: Applying DDD Chapter 4. A New Default Architecture The Basis of the New Default Architecture A First Sketch Making a First Attempt at Hooking the UI to the Domain Model Yet Another Dimension Summary Chapter 5. Moving Further with Domain-Driven Design Refining the Domain Model Through Simple TDD Experimentation Fluent Interface
Summary Chapter 6. Preparing for Infrastructure POCO as a Lifestyle Dealing with Save Scenarios Let's Build the Fake Mechanism Database Testing Querying Summary Chapter 7. Let the Rules Rule Categorization of Rules Principles for Rules and Their Usage Starting to Create an API Requirements for a Basic Rules API Related to Persistence Focus on Domain-Related Rules Extending the API Refining the Implementation Binding to the Persistence Abstraction Generics and Anonymous Methods to the Rescue What Others Have Done Summary Part III: Applying PoEaa Chapter 8. Infrastructure for Persistence Requirements on the Persistence Infrastructure Where to Store Data Approach Classification Another Classification: Infrastructure Patterns Summary Chapter 9. Putting NHibernate into Action Why NHibernate? A Short Introduction to NHibernate Requirements of the Persistence Infrastructure Classification Another Classification: Infrastructure Patterns NHibernate and DDD Summary Part IV: What's Next? Chapter 10. Design Techniques to Embrace Context Is King An Introduction to SOA Inversion of Control and Dependency Injection Aspect-Oriented Programming (AOP) Summary Chapter 11. Focus on the UI A Prepilogue The Model-View-Controller Pattern Test-Driving a Web Form Mapping and Wrapping Summary Epilogue
Part V: Appendices Appendix A. Other Domain Model Styles Object-Oriented Data Model, Smart Service Layer, and Documents The Database Model Is the Domain Model Pragmatism and the Nontraditional Approach Summary Appendix B. Catalog of Discussed Patterns Abstract Factory [GoF Design Patterns] Aggregate [Evans DDD] Bounded Context [Evans DDD] Chain of Responsibility [GoF Design Patterns] Class Table Inheritance [Fowler PoEAA] Coarse-Grained Lock [Fowler PoEAA] Collecting Parameter Pattern [Beck SBPP] Concrete Table Inheritance [Fowler PoEAA] Data Mapper [Fowler PoEAA] Data Transfer Objects [Fowler PoEAA] Decorator [GoF Design Patterns] Dependency Injection Domain Model [Fowler PoEAA] Embedded Value [Fowler PoEAA] Entity [Evans DDD] Factory [Evans DDD] Factory Method [GoF Design Patterns] Foreign Key Mapping [Fowler PoEAA] Generation Gap [Vlissides Pattern Hatching] Identity Field [Fowler PoEAA] Identity Map [Fowler PoEAA] Implicit Lock [Fowler PoEAA] Layer Supertype [Fowler PoEAA] Layers [POSA] Lazy Load [Fowler PoEAA] Metadata Mapping [Fowler PoEAA] Model View Controller [Fowler PoEAA] Model View Presenter [Fowler PoEAA2] Notification [Fowler PoEAA2] Null Object [Woolf Null Object] Optimistic Offline Lock [Fowler PoEAA] Party Archetype [Arlow/Neustadt Archetype Patterns] Pessimistic Offline Lock [Fowler PoEAA] Pipes and Filters [POSA] Presentation Model [Fowler PoEAA2] Proxy [GoF Design Patterns] Query Object [Fowler PoEAA] Recordset [Fowler PoEAA] Reflection [POSA] Registry [Fowler PoEAA] Remote Façade [Fowler PoEAA] Repository [Evans DDD] Separated Presentation [Fowler PoEAA2]
Service Layer [Fowler PoEAA] Service Locator [Alur/Crupi/Malks Core J2EE Patterns] Services [Evans DDD] Single Table Inheritance [Fowler PoEAA] Singleton [GoF Design Patterns] Specification [Evans DDD] State [GoF Design Patterns] Table Module [Fowler PoEAA] Template Method [GoF Design Patterns] Transaction Script [Fowler PoEAA] Unit of Work [Fowler PoEAA] Value Object [Evans DDD] References Index
Copyright Many of t he designat ions used by m anufact urers and sellers t o dist inguish t heir product s are claim ed as t radem arks. Where t hose designat ions appear in t his book, and t he publisher was aware of a t radem ark claim , t he designat ions have been print ed wit h init ial capit al let t ers or in all capit als. The aut hor and publisher have t aken care in t he preparat ion of t his book, but m ake no expressed or im plied warrant y of any kind and assum e no responsibilit y for errors or om issions. No liabilit y is assum ed for incident al or consequent ial dam ages in connect ion wit h or arising out of t he use of t he inform at ion or program s cont ained herein. The publisher offers excellent discount s on t his book when ordered in quant it y for bulk purchases or special sales, which m ay include elect ronic versions and/ or cust om covers and cont ent part icular t o your business, t raining goals, m arket ing focus, and branding int erest s. For m ore inform at ion, please cont act :
U.S. Corporat e and Governm ent Sales ( 800) 382- 3419 corpsales@pearsont echgroup.com For sales out side t he Unit ed St at es please cont act :
I nt ernat ional Sales int ernat [email protected] Visit us on t he Web: www.awprofessional.com
Library of Congress Cat aloging- in- Publicat ion Dat a Nilsson, Jim m y. Applying dom ain- driven design and pat t erns wit h exam ples in C# and .NET / Jim m y Nilsson. p. cm . I ncludes bibliographical references. I SBN 0- 321- 26820- 2 1. Com put er soft ware- Developm ent . 2. C# ( Com put er program language) 3. Microsoft .NET. I . Tit le. QA76.76.D47N645 2006 005.1- - dc22 2006004371 Copyright © 2006 Pearson Educat ion, I nc. All right s reserved. Print ed in t he Unit ed St at es of Am erica. This publicat ion is prot ect ed by copyright , and perm ission m ust be obt ained from t he publisher prior t o any prohibit ed reproduct ion, st orage in a ret rieval syst em , or t ransm ission in any form or by any m eans, elect ronic, m echanical, phot ocopying, recording, or likewise. For inform at ion regarding perm issions, writ e t o:
Pearson Educat ion, I nc. Right s and Cont ract s Depart m ent 75 Arlingt on St reet , Suit e 300 Bost on, MA 02116 Fax: ( 617) 848- 7047 Text print ed in t he Unit ed St at es on recycled paper at R. R. Donnelley in Crawfordsville, I ndiana. First print ing, May 2006
Dedication To Lot t a, Tim , and Leo: t he cent ers of m y universe.
Praise for Applying Domain-Driven Design and Patterns " I don't know what it was I professed t o doing before I had added Dom ain- Driven Design and Test - Driven Developm ent t o m y t oolkit , but from m y present perspect ive, I 'm ret icent t o call it anyt hing but chaot ic hacking. Dom ain- Driven Design and Test - Driven Developm ent are t wo approaches t hat have consist ent ly guided m e t oward a pract ical applicat ion of soft ware design principles, and brought m y proj ect s t o fruit ion wit h healt hy, sust ainable soft ware. This is a book t hat put s it s m oney where it s m out h is in t erm s of concret ely com m unicat ing Agile soft ware developm ent pract ice. I t 's pot ent ially one of t he m ost im pact ful guides t o soft ware developm ent success pract ices yet offered t o t he .NET soft ware developm ent com m unit y." Scot t Bellware, Microsoft MVP for C# , DDD and TDD Blogger " Jim m y Nilsson does his readers a great service by showing t hem how t o apply t he foundat ional principles of ent erprise applicat ion design and developm ent t aught by Evans, Fowler, and ot her t hought leaders. The book uses a worked exam ple not only t o explain, but also t o dem onst rat e Dom ain- Driven Design, Pat t erns of Ent erprise Applicat ion Archit ect ure, and Test - Driven Developm ent . Jim m y's insight and experience m ake reading it a pleasure, and leave t he reader wit h t he cert aint y t hat t hey have learned from a m ast er pract it ioner. Ent erprise developers looking t o m ast er t hese principles will find t he book a valuable guide." Jack Greenfield, Ent erprise Tools archit ect , Visual St udio Team Syst em , Microsoft " Good soft ware archit ect s reserve t he right t o get sm art er. Beyond t he goal of shipping t heir current syst em , t hey're on a quest t o discover bet t er ways t o design and build soft ware. This book is t ravelogue of sort s in which Jim m y docum ent s his j ourney t hrough a wide range of pat t erns, pract ices, and t echnologies, explaining how his t hinking about ent erprise syst em s has evolved along t he way. I f you're t raveling t he sam e road, t his book is a good com panion." Tim Ewald, principal archit ect at Foliage Soft ware Syst em s and aut hor of Transact ional COM+ : Building Scalable Applicat ions " This book does an excellent j ob at m aking t angible t he large but very im port ant ideas of Dom ain- Driven Design." Floyd Marinescu, aut hor of EJB Design Pat t erns and creat or of I nfoQ.com and TheServerSide.com " Underst anding t he concept s and driving forces in t he problem dom ain is param ount t o t he success of soft ware developm ent . Jim m y Nilsson has drawn inspirat ion from t he past t en years of st udies int o pat t erns and Dom ain- Driven Design as well as recorded his own experiences from concret e developm ent proj ect s. This book cont ains com pelling exam ples of how t heory can be t ranslat ed int o pract ice. I t dem onst rat es t he aut hor's profound underst anding of design choices and t rade- offs involved in obj ect - orient ed developm ent ." Anders Hessellund, I T Universit y of Copenhagen, Denm ark " This book t ackles an area t hat is a challenge for m ost developers on t he .NET plat form . As t he pat t erns and pract ices archit ect who init iat ed and drove t he effort s around ent erprise guidance
for .NET, I know how im port ant t his area is for our cust om ers, and am painfully aware of t he gaps t hat st ill exist in our guidance. " I was t hrilled t o see Jim m y would be sharing his insight s based on his experience doing DDD and TDD. I believe t his t opic can be best t ackled at t his point in t im e t hrough a focus on sim plicit y, pat t erns, and awareness of t he social aspect s of building applicat ions. " I t rust Jim m y's experience and knowledge of .NET, and have enj oyed his st yle of sharing concept s and st ories. I can hardly im agine som eone bet t er suit ed t o explain t his t opic on t he plat form I work on every day. " I will definit ively recom m end Jim m y's book t o m y cust om ers, m y t eam , and ot her Microsoft engineer s. " Ult im at ely, I hope t hat our indust ry can get bet t er at expressing rich concept s in code, and t hat it get s bet t er at t he social process of art iculat ing and evolving shared knowledge. TDD and DDD lie t oget her at t he heart of t his pat h." Edward Jezierski, archit ect of Microsoft pat t erns and pract ices " Jim m y has woven t oget her best - of- bread t echniquesDom ain- Driven Design, Test - Driven Developm ent , refact oring, and design pat t ernsint o a convincing alt ernat ive t o t he Dat a- Driven Developm ent st yle t hat has been t he m ainst ay of Microsoft applicat ions. The core of t he book breat hes life int o t he t echniques advocat ed in Dom ain- Driven Design; it is relent lessly pract ical, gives t he reader insight int o t he t hought process t hat underlies applying proven t echniques and t echnologies, and, like all of Jim m y's work, is an easy read well done Jim m y! " Christ ian Crowhurst , analyst developer " I 've been working on a j oint proj ect wit h Jim m y for 18 m ont hs now. Jim m y really lives as he preaches. I n t his book he t ells you how he is working in pract ice. He uses O/ R Mapping and NHibernat e daily and he also really uses TDD t o help underst and and dist ill t he cust om er's business logic and t o m ake sure t hat he only delivers t est ed, high- qualit y code. Just as he delivers a high- qualit y book cont aining t est ed concept s t o you. Enj oy." Dan Byst röm , soft ware developer, www.visual- design.se/ blog " By showing how t o apply Dom ain- Driven Design and ent erprise applicat ion pat t erns, Jim m y m akes a difficult t opic approachable for a larger audience. This book has som et hing for t he experienced archit ect and aspiring pat t ern im plem ent er alike. I n fact , any developer or archit ect who want s t o im prove t he way he/ she designs and im plem ent s soft ware solut ions should read t his book." Per- Ola Nilsson, head of developm ent and soft ware archit ect for Luvit " Const ant ly abreast wit h t he lat est t rends in soft ware developm ent but never swept away by t he hype, Jim m y expert ly ext ract s t he value and leaves t he fluff behind. Always hum ble and wit h bot h his ears wide open, he is uniquely well suit ed t o guide and enlight en t he reader using t he pract ical applicabilit y as t he st andard, shunning t he ivory t ower. Jim m y doesn't offer his help from above he act ually m anages t o convince us t hat we have j ust learned about t he cut t ing edge from a peer." Mat s Helander, soft ware developer, www.m at shelander.com " As wit h Jim m y's previous book, t his book is a t rue m ast erpiece: a well- laid out balance bet ween pragm at ic soft ware engineering and deep t heory." Frans Boum a, creat or of LLBLGen Pro, weblogs.asp.net / fboum a
" Wit h t his book being based on solid ground, Jim m y t ackles t he real issues in applying Dom ainDriven Design pract ices. Finally we have a m anual t o keep us from hit t ing t he wall in coping wit h ever- increasing com plexit y in t om orrow's dom ain m odel- based ent erprise applicat ions." Paul Gielens, senior consult ant for Capgem ini, weblogs.asp.net / pgielens " Speaking from experience, com bining t he subst ance behind acronym s such as DDD, DP, and TDD in .NET can lead t o very efficient and adapt ive ent erprise soft ware archit ect ures. This book out lines how, and why, t hese concept s t oget her m ake such a powerful whole. Hence, t he reader is given an invaluable short cut t o successful ent erprise soft ware archit ect ures, m aking it a m ust read for any developer of such." Mart in Rosén- Lidholm , soft ware archit ect for Exense Healt hcare " Do you want t o build a high- qualit y ent erprise syst em wit h obj ect orient at ion and a relat ional dat abase? Don't m iss t his one. Jim m y shows you how Test - Driven Developm ent can be t he driving force t hroughout t he whole proj ect . He shows how t o m ap t he OO design t o t he dat abase, a st ep oft en t reat ed very briefly in lit erat ure, which alone m akes t he book wort h t he m oney. There are also plent y of design t ips and discussions so you can follow his reasoning." I ngem ar Lundberg, passionat e soft ware developer, w w w .ingolundberg.com " This im port ant and t im ely book is a m ust read for anyone want ing t o get int o Dom ain- Driven Design in C# ." Gregory Young, Microsoft MVP for C# , independent consult ant " This book deals wit h several im port ant concept s used in m odern soft ware developm ent , such as Test - Driven Developm ent , refact oring, pat t erns, and of course, Dom ain- Driven Design. Jim m y Nilsson writ es about t hese t hings in a very conversat ional t one, and you alm ost get t he feeling t hat he is act ually sit t ing next t o you, showing you his exam ples and having a conversat ion about t he pros and cons of different solut ions wit h you personally." Niclas Nilsson, soft ware developer and educat or for Act iva; not a brot her t o Jim m y, not even relat ed " This book does an excellent j ob of bringing Dom ain- Driven Design ( DDD) int o pract ical cont ext by bridging t he gaps bet ween different abst ract ion levels st art ing from t he business perspect ive down t o t he syst em level. The book is ideal for soft ware archit ect s and developers because it is writ t en in a way t hat allows it t o be used as a prim er or reference. The aut hor excels in present ing all aspect s of DDD in a way t hat is not only educat ional but also fun t o read." Gunt her Lenz, program m anager and aut hor " Not so very long ago, I blogged about m y overall feeling t hat as archit ect s we m ay be failing t o provide enough guidance for developers t o sort t hrough t he hundreds of different ways t o build soft ware. There are m any ideas out t here t hat are excellent by t hem selves and com e wit h perfect ly well- reasoned j ust ificat ions, working exam ples, and an im passioned com m unit y of believers. Very few of t hem can be used by t hem selves t o build applicat ions, m uch less syst em s of applicat ions and services. This leaves it up t o developers t o find ways t o put t he individual ideas t oget her int o applicat ions. This is always m uch harder in realit y t han t he proponent s of any part icular approach, like t o t hink about . Developers, on t he ot her hand, need t o get som e work done and oft en don't care t o wade t hrough all t his every t im e t hey st art a new applicat ion. This is t rue even when t hey are t ruly int erest ed in t he pot ent ial gains of new approaches or ideas. Jim m y's book is point ed direct ly at t his problem . How do you apply som e of t he great ideas t hat have com e forward in t he last few years t o real, live developm ent ? I t hink t his book does a great j ob of bot h explaining t he underlying ideas wit h enough det ail t o m ake t hem
pract ical and put t ing t he group of ideas t oget her in a way t hat will help developers see a pat h from st art t o end. Dom ain- Driven Design, Test - Driven Developm ent , Dependency I nj ect ion, persist ence, and m any ot her pract ical issues are covered. Aft er reading t his book, I t hink a reader will really be able t o t ake t hese im port ant ideas and use t hem t oget her effect ively. Considering t he great value of t he individual ideas present ed, but t he lim it ed num bers of exam ples of people really using t hem , t his work represent s a great win for us all. I highly recom m end it ." Philip Nelson, chief scient ist for PAi, xcskiwinn.org/ com m unit y/ blogs/ panm anphil/ default .aspx " Taking t he leap from a dat a- cent ric m indset t o t he deeper realizat ion of OOP found in Dom ainDriven Design, Test - Driven Developm ent , obj ect / relat ional persist ence, and ot her Agile m et hods and pat t erns can be an arduous and disorient ing undert aking for t he uninit iat ed. " Wit h a pat ient , pragm at ic, and m ent oring st yle, Jim m y t akes t he reader along for t he leap, exploring t he issues and t he opt ions, giving sound advice along t he way. This book shows you how t o int egrat e t he various m et hods and pat t erns int o a fully coherent approach t o designing and creat ing superbly m aint ainable .Net soft ware." George Hicks, senior developer for Propert y Works " I f you have ever read his blog, you already know t hat Jim m y Nilsson likes t o challenge est ablished 't rut hs' in t he .NET com m unit y, looking for bet t er ways t o design soft ware. He acknowledges t hat soft ware design is hard, and t hat one- size- fit s- all solut ions do not work; pros and cons have t o be balanced in cont ext of t he problem at hand before ruling a winner. For building t est able and m aint ainable soft ware wit h com plex business requirem ent s, Jim m y chooses t o use Dom ain- Driven Design, and he brings along a t oolbox of proven and est ablished principles, pat t erns, and pract ices t o carry it out . Jim m y's inform al writ ing st yle and use of exam ples and Test - Driven Developm ent m ake t his book very approachable, especially considering all t he ground t hat is covered. Because he is on t op of t he DDD st uff, Jim m y ( and friends) brings you dist illed knowledge on, and references t o, m any of t he m ost valuable t echniques and resources wit hin soft ware developm ent t oday. I believe t his book t o be a valuable guide t o applying DDD, and for developers who want t o im prove t heir general design skills." Andreas Brink, soft ware developer " I n Applying Dom ain- Driven Design and Pat t erns, Jim m y Nilsson st rengt hens his posit ion as an aut horit y on applied soft ware archit ect ure. Wit h a nice blend of his personal t hought s on t he subj ect s, Jim m y t akes t he reader on a t our t hrough m ost of t he m odern m ust - know design t echniques, leaving not hing unt ouched. Jim m y shows how t o im plem ent t he t hought s of ot her, m ore t heoret ical t hought leaders in an appealing and easy t o follow st ruct ure. I am cert ain t hat Applying Dom ain- Driven Design and Pat t erns will becom e a m andat ory t it le in t he ent erprise bookshelf." Mikael Freidlit z, vice president of Cont ent and Knowledge Program s at I ASA " Dom ain- Driven Design is an im port ant t echnique t hat can help produce qualit y business applicat ions t hat evolve wit h t he needs of t he business. I n an ideal world, pract icing DDD would be about OO designbut in realit y, t he t echnologies we work wit h im pose num erous const raint s. " This book t ackles t hat challenge head on, bridging t he gap bet ween DDD concept s and t he act ion of t ranslat ing t hem int o pract ice on t he .NET plat form . Jim m y not only has a deep underst anding of DDD and ent erprise t echnologies, but has learned m any lessons from his ext ensive indust ry experience, and t akes a refreshingly pragm at ic approach. This is a valuable book."
Rod Johnson, founder of Spring Fram ework, CEO of I nt erface21 " This is a great book. I t t akes you on a hands- on inform at ive t ravel in t he world of Dom ainDriven Design. A great num ber of im port ant issues are discussed, explained, and shown in relevant cont ext s. These will give you a great foundat ion when you are working wit h your own Dom ain Model- based syst em . Bot h t he act ual developm ent t echniques as well as pract ical work m et hods ( as incorporat ion of Test - Driven Developm ent ) are discussed." Trond- Eirik Kolloen, soft ware archit ect and developer " Can we focus on t he house proj ect s now?" Lot t a, wife " Aaa, dee, dee, dee, pee..." ( Swedish pronunciat ion of t he book abbreviat ion) Leo, son, four years old " Dad, do you really t hink som eone will read it ?" Tim , son, eight years old
About the Author Jim m y N ilsson owns and runs t he Swedish consult ing com pany JNSK AB. He has writ t en num erous t echnical art icles and t wo books. He has also been t raining and speaking at conferences, but above everyt hing else, he is a developer wit h alm ost 20 years of experience ( www.j nsk.se/ weblog/ ) .
Forewords Building ent erprise soft ware is rarely easy. Alt hough we have a plet hora of t ools and fram eworks t o m ake it easier, we st ill have t o figure out how t o use t hese t ools well. There are lot s of approaches you can t ake, but t he t rick is knowing which one t o use in specific sit uat ionshardly ever does one approach work in all cases. Over t he last few years t here's grown up a com m unit y of people looking t o capt ure approaches t o design ent erprise applicat ions and docum ent t hem in t he form of pat t erns ( I keep an overview wit h links at ht t p: / / m art infowler.com / art icles/ ent erprisePat t erns.ht m l) . People involved in t his effort , such as m e, t ry t o find com m on approaches and describe how t o do t hem well and when t hey are applicable. The result ing work is pret t y wide ranging, and t hat can lead t o t oo m uch choice for t he reader. When I st art ed writ ing Pat t erns of Ent erprise Applicat ion Archit ect ure ( Addison- Wesley, 2002) , I looked for t his kind of design advice in t he Microsoft world. I st ruggled t o find m uch of anyt hing, but one rare book t hat t ackled t he t errit ory was Jim m y's earlier book. I liked his inform al writ ing st yle and eagerness t o dig int o concept s t hat m any ot hers skim m ed over. So it 's fit t ing t hat Jim m y decided t o t ake m any of t he ideas from m e and t he ot hers in t he ent erprise pat t erns com m unit y and show how you can apply t hem in writ ing .NET applicat ions. The focus of t his ent erprise pat t erns com m unit y is docum ent ing good designs, but anot her t hread runs t hrough us. We are also big fans of agile m et hods, em bracing t echniques such as Test - Driven Developm ent ( TDD) and refact oring. So Jim m y also brought t hese ideas int o t his book. Many people t hink t hat pat t ern- people's focus on design and TDD's focus on evolut ion are at odds. The huge overlap bet ween pat t ern- people and TDDers shows t his isn't t rue, and Jim m y has weaved bot h of t hese t hreads int o t his book. The result is a book about design in t he .NET world, driven in an agile m anner and infused wit h t he product s of t he ent erprise pat t erns com m unit y. I t 's a book t hat shows you how t o begin applying such t hings as TDD, obj ect - relat ional m apping, and dom ain- driven design t o .NET proj ect s. I f you haven't yet com e across t hese concept s, you'll find t hat t his book is an int roduct ion t o t echniques t hat m any developers t hink are t he key for fut ure soft ware developm ent . I f you are fam iliar wit h t hese ideas, t he book will help you pass t hose ideas on t o your colleagues. Many people feel t he Microsoft com m unit y has not been as good as ot hers in propagat ing good design advice for ent erprise applicat ions. As t he t echnology becom es m ore capable and sophist icat ed, it becom es m ore im port ant t o underst and how t o use it well. This book is a valuable st ep in advancing t hat underst anding. M a r t in Fow le r h t t p:/ / m a r t in fow le r .com The best way t o learn how t o do Dom ain- Driven Design ( DDD) is t o sit down next t o a friendly, pat ient , experienced pract it ioner and work t hrough problem s t oget her, st ep- by- st ep. That is what reading t his book is like. This book does not push a new grand schem e. I t unaffect edly report s on one expert pract it ioner's use of and com binat ion of t he current pract ices he has been drawn t o. Jim m y Nilsson reit erat es what m any of us have been saying: t hat several current ly t rendy t opicsspecifically, DDD, Pat t erns of Ent erprise Applicat ion Archit ect ure ( PoEAA) , and Test - Driven Developm ent ( TDD) are not alt ernat ives t o each ot her, but are m ut ually reinforcing elem ent s of
successful developm ent . Furt herm ore, all t hree of t hese are harder t han t hey look at first . They require ext ensive knowledge over a wide range. This book does spend som e t im e advocat ing t hese approaches, but m ost ly it focuses on t he det ails of how t o m ake t hem work. Effect ive design is not j ust a bunch of t echniques t o be learned by rot e; it is a way of t hinking. As Jim m y dives int o an exam ple he gives us a lit t le window int o his m ind. He not only shows his solut ion and explains it , he let s us see how he got t here. When I am designing som et hing, dozens of considerat ions flit t hrough m y m ind. I f t hey are fact ors I 've dealt wit h oft en, t hey pass so quickly I am barely conscious of t hem . I f t hey are in areas where I have less confidence, I dwell on t hem m ore. I presum e t his is t ypical of designers, but it is difficult t o com m unicat e t o anot her person. As Jim m y walks t hrough his exam ples, it is as if he were slowing t his process down t o an observable pace. At every lit t le j unct ure, t hree or four alt ernat ives present t hem selves and get weighed and rej ect ed in favor of t he one he event ually chooses. For exam ple, we want m odel obj ect s t hat are im plem ent ed free of ent anglem ent wit h t he persist ence t echnology. So what are eight ways ( eight ! ) t hat a persist ence fram ework can force you t o cont am inat e t he im plem ent at ion of a dom ain obj ect ? What considerat ions would lead you t o com prom ise on som e of t hese point s? What do t he current ly popular fram eworks, including t he .NET plat form , im pose? Jim m y t hinks pragm at ically. He draws on his experience t o m ake a design choice t hat will likely t ake him t oward t he goal, adhering t o t he deeper design principle, rat her t han t he choice t hat looks t he m ost like a t ext book exam ple. And all of his decisions are provisional. The first design principle Jim m y holds in front of him self is t he fundam ent al goal of DDD: a design t hat reflect s deep underst anding of t he business problem at hand in a form t hat allows adapt at ion t o new wrinkles. So why so m uch discussion of t echnical fram ework and archit ect ure? I t is a com m on m ispercept ion, perhaps a nat ural one, t hat such a priorit y on t he dom ain dem ands less t echnical t alent and skill. Would t hat t his were t rue. I t would not be quit e so difficult t o becom e a com pet ent dom ain designer. I ronically, t o render clear and useful dom ain concept s in soft ware, t o keep t hem from being suffocat ed under t echnical clut t er, requires part icularly deft use of t echnology. My observat ion is t hat t hose wit h t he great est m ast ery of t echnology and archit ect ural principles oft en know how t o keep t echnology in it s place and are am ong t he m ost effect ive dom ain m odelers. I do not refer t o t he knowledge of every quirk of com plex t ools, but t o t he m ast ery of t he sort of knowledge laid out in Mart in Fowler's PoEAA, because naïve applicat ion of t echnology paradoxically m akes t hat t echnology m ore int rusive int o t he applicat ion. For m any people t his book will fill in gaps of how t o im plem ent expressive obj ect m odels in pract ice. I picked up a num ber of useful ways of t hinking t hrough t he applicat ion of t echnical fram eworks, and I especially firm ed up m y underst anding of som e part iculars of doing DDD in a .NET set t ing. I n addit ion t o t echnical archit ect ure, Jim m y spends a great deal of t im e on how t o writ e t est s. TDD com plem ent s DDD in a different way. I n t he absence of a focus on refining an ever m ore useful m odel, TDD is prone t o fragm ent ed applicat ions, where a single- m inded at t ack on one feat ure at a t im e leads t o an unext endable syst em . A com prehensive t est suit e act ually allows such a t eam t o cont inue m aking progress longer t han would be possible wit hout it , but t his is j ust t he basest value of TDD. At it s best , t he t est suit e is t he laborat ory for t he dom ain m odel and a t echnical expression of t he ubiquit ous language. Test s of a part icular st yle drive t he m odeling process forward and keep it focused. This book st eps us t hrough exam ples of developing such t est s. Jim m y Nilsson has a rare com binat ion of self- confidence and hum ilit y, which I have observed t o be
charact erist ic of t he best designers. We get a glim pse of how he got t o his current underst anding as he t ells us what he used t o believe and why t hat opinion changed, which helps t o t ake t he reader past t he specifics of t he t echniques t o t he underlying principles. This hum ilit y m akes him open t o a wide range of influences, which gives us t his fusion of ideas from different sources. He has t ried a lot of t hings and has let his result s and experience be his guide. His conclusions are not present ed as revealed t rut h, but as his best underst anding so far wit h an im plicit recognit ion t hat we never have com plet e knowledge. All t his m akes t he advice m ore useful t o t he reader. And t his at t it ude, in it self, illust rat es an im port ant elem ent of successful soft ware developm ent leadership. Er ic Eva n s
Preface: Bridging Gaps On t he cover of t his book is a pict ure of t he Øresund Bridge t hat connect s Sweden and Denm ark. I t seem s t hat all soft ware archit ect ure books m ust have a bridge on t he cover, but t here are som e addit ional reasons t he bridge is appropriat e for t his book. This bridge replaced a ferry t hat I t ook m any t im es as a child. I enj oy very m uch driving over it even aft er dozens of t im es. On a personal not e, m y fat her was on t he t eam t hat built t he highest part s of t he bridge. But beyond t hese, t he m ain reason is t hat t his book is very m uch about bridging gaps; bridging gaps bet ween users and developers; bridging gaps bet ween business and soft ware; bridging gaps bet ween logic and st orage. Bridging gaps bet ween " DB- guys" and " OO- guys" ... I will refrain from m aking a j oke about t he Bridge pat t ern [ GoF Design Pat t erns] . Hey, how geeky can a preface be?
Focus of This Book The m ain focus of t he book is how a Dom ain Model could be const ruct ed t o be clean, yet st ill be persist ence- friendly. I t shows what t he persist ence solut ion could look like for such a Dom ain Model and especially how t o bridge t hat gap bet ween t he Dom ain Model and t he dat abase. Put anot her way, m y vision has been t o provide a book t hat will put Eric Evans' Dom ain- Driven Design [ Evans DDD] and Mart in Fowler's Pat t erns of Ent erprise Applicat ion Archit ect ure [ Fowler PoEAA] in cont ext . DDD m ight be perceived as a bit abst ract . Therefore, m ore concret e exam ples are helpful regarding persist ence, for exam ple. Mine m ay be fairly basic, but it is a plat form t o st art from . This book not only explains how t o use t he pat t erns, but also how t he pat t erns are used in O/ R Mappers, for exam ple. I t has becom e very clear t o m e t hat " one size does not fit all" when it com es t o archit ect ure. Having said t hat , pat t erns have proven t o be general enough t o use and reuse in cont ext aft er cont ext . The focus isn't on t he pat t erns t hem selves, but t his book uses pat t erns in every chapt er as a t ool and language for discussing different design aspect s. A nice side effect is t hat pat t erns- ignorant readers will also gain som e insight and int erest int o pat t erns along t he way. That also goes for TDD. Not all developers have becom e int erest ed in t his yet . I t hink it 's especially com m on in t he .NET com m unit y t hat TDD ( j ust as pat t erns) is considered a niche t echnique at best , or it m ight even be t ot ally unknown. Readers will learn how t o apply TDD.
Why This Book? Writ ing m y first book [ Nilsson NED] was a really t ough proj ect on t op of all m y ot her ordinary
proj ect s and obligat ions. I was pret t y sure I wouldn't writ e anot her, but t he t im e cam e when I t hought I had som et hing t o say t hat I couldn't leave unsaid. My change of heart st art ed when I read t wo recent books t hat inspired m e and changed m y t hinking. First , t here was Mart in Fowler's Pat t erns of Ent erprise Applicat ion Archit ect ure [ Fowler PoEAA] . This book inspired m e t o give t he Dom ain Model pat t ern anot her t ry aft er having failed wit h several earlier at t em pt s. Then I read Eric Evans' book Dom ain- Driven Design [ Evans DDD] . This book provided m e wit h insight s about how t o t hink and act regarding developm ent wit h a st rong dom ain focus and wit h a cert ain st yle of how t o apply t he Dom ain Model pat t ern. Anot her im port ant influence was all t hat I learned from t eaching m y pat t erns course over a couple of years. As I int eract ed wit h st udent s and t he m at erial evolved, I had insight s m yself. My views of DDD t ransform ed as I worked on an am bit ious ( t hough unfort unat ely unfinished) open source proj ect called Valhalla, which I developed in collaborat ion wit h Christ offer Skj oldborg. ( Christ offer did by far t he m ost work.) To sum m arize all t his, I felt t hat a book t hat dealt m ore wit h applicat ion t han t heory was needed, but one t hat was based on solid ground, such as t he DDD and PoEAA books. " Applying" feels close t o m y heart because I consider m yself a developer above anyt hing else.
Target Audience This book is aim ed at a wide t arget audience. I t will help if you have som e knowledge of Obj ect - orient at ion .NET or a sim ilar plat form C# or a sim ilar language Relat ional dat abases; for exam ple, SQL Server However, int erest and ent husiasm will com pensat e for any lack of prior experience. I 'd like t o elaborat e on m y st at em ent t hat t he t arget audience is wide. First , we can t hink about t he way we put people int o plat form boxes. The book should serve .NET people who want a m ore corebased approach t han drag- t ill- you- drop ( if I m ay use som e weak generalizat ions) . Java people should get som et hing out of t he discussions and exam ples of how t o com bine DDD and O/ R Mapping. I t hink t he chosen language/ plat form is less and less im port ant , so it feels a lit t le st range t o t alk about .NET people and Java people. Let 's t ry t o describe t he t arget audience by using anot her dim ension. Then I t hink t hat t he book is for developers, t eam leaders, and archit ect s. Choosing yet anot her dim ension, I t hink t here m ight be som et hing in t his book bot h for int erm ediat e and advanced readers. There's probably also som et hing for beginners.
Organization of This Book The book is arranged in four part s: " Background," " Applying DDD," " Applying PoEAA," and " What 's Next ?"
Part I: Background I n t his part , we discuss archit ect ure and processes in general t erm s. There is a lot of em phasis on Dom ain Models and DDD [ Evans DDD] . We also int roduce pat t erns and TDD. The chapt ers include t he following: Chapt er 1, " Values t o Value" This chapt er discusses propert ies of archit ect ure and process t o value for creat ing qualit y result s when it com es t o syst em developm ent . The discussion is influenced by Ext rem e Program m ing. Chapt er 2, " A Head St art on Pat t erns" This chapt er focuses on providing exam ples and discussions about pat t erns from different fam ilies, such as design pat t erns, archit ect ural pat t erns and dom ain pat t erns. Chapt er 3, " TDD and Refact oring" Chapt er 1 t alks quit e a lot about TDD and refact oring, but in t his chapt er t here is m ore in- dept h coverage wit h pret t y long exam ples and also different flavors of TDD.
Part II: Applying DDD I n t his part , it 's t im e t o apply DDD. We also prepare t he Dom ain Model for t he infrast ruct ure, and focus quit e a lot on rules aspect s. Chapt er 4, " A New Default Archit ect ure" This chapt er list s a set of requirem ent s of an exam ple applicat ion, and a first - t ry m odel is creat ed as a st art for t he com ing chapt ers. A Dom ain Model- based archit ect ure is used. Chapt er 5, " Moving Furt her wit h Dom ain- Driven Design" The requirem ent s set up in t he prior chapt er are used in t his chapt er as t he basis for slowly, wit h TDD, st art ing t o build t he Dom ain Model in a DDD- ish st yle. Chapt er 6, " Preparing for I nfrast ruct ure" Even t hough we t ry t o push t he infrast ruct ure aspect s as far off in t he fut ure as possible, it 's good t o t hink a lit t le bit ahead and prepare t he Dom ain Model for t he infrast ruct ure needs. I n t his chapt er, t here is a lot of discussion about pros and cons of Persist ence I gnorant Dom ain Models. Chapt er 7, " Let t he Rules Rule" This chapt er t alks about business rules in t he form of validat ion and how a Dom ain Model- based solut ion can deal wit h t he need for such rules, connect ing back t o t he requirem ent s set up in Chapt er 4.
Part III: Applying PoEAA I n t his part , we put several of t he pat t erns in Fowler's Pat t erns of Ent erprise Applicat ion Archit ect ure [ Fowler PoEAA] int o cont ext by discussing what we need from t he infrast ruct ure for providing persist ence support t o our Dom ain Model. We will t ake a look at how t hose requirem ent s are fulfilled by an exam ple t ool. Chapt er 8, " I nfrast ruct ure for Persist ence" When we have a fairly good Dom ain Model, it 's t im e t o t hink about infrast ruct ure, and t he m ain t ype of infrast ruct ure in t his book is infrast ruct ure for persist ence. This chapt er discusses different propert ies of persist ence solut ions and how t o cat egorize a cert ain solut ion. Chapt er 9, " Put t ing NHibernat e int o Act ion" This chapt er uses t he cat egorizat ions of t he prior chapt er wit h an exam ple of a persist ence solut ion, nam ely NHibernat e [ NHibernat e] .
Part IV: What's Next? I n t his part , t here is a focus on ot her design t echniques t o keep an eye on and st art using. The ot her focus is on how you can deal wit h t he present at ion layer when it com es t o bridging t hat gap t o t he Dom ain Model, but also how t o deal wit h developer t est ing of t he UI . This part is alm ost exclusively writ t en by guest aut hors. Chapt er 10, " Design Techniques t o Em brace" Aft er a short discussion about Bounded Cont ext , t his chapt er discusses design t echniques t o keep an eye on now and for t he fut ure, such as Service Orient at ion, Dependency I nj ect ion/ I nversion of Cont rol, and Aspect Orient at ion. Chapt er 11, " Focus on t he UI " This chapt er focuses on how t he UI can be connect ed t o t he Dom ain Model and how t o increase t est abilit y for t he user int erface when using a Dom ain Model, bot h for rich client applicat ions and Web applicat ions.
Appendices There are t wo appendices providing furt her exam ples of Dom ain Model st yles and an overview- t ype pat t erns cat alog.
Why C# for the Examples? I n no way is t his a book for t eaching C# . But I st ill need a language ( or possibly several, but I have chosen one) for t he exam ples, and t hat 's where C# com es in. The reasons for t he choice are m ainly t hat C# is m y current m ain language and t hat m ost VB.NET and Java developers can read C# code pret t y easily.
Regarding t he version, m ost of t he code exam ples work in bot h C# 1.1 and 2.0, but t here are som e rare sect ions t hat are focused on 2.0.
Topics That Aren't Covered There are loads of t opics t hat aren't covered in t he book, but I t hink t here are t wo m issing t opics t hat spring t o m ind. They are dist ribut ion and advanced m odeling.
Distribution I t was in m y early plans of t he book t o include t horough coverage of t he dist ribut ion aspect s, but lat er on I cam e t o t he conclusion t hat t he book would becom e t oo unfocused. St ill, t here is som e coverage here and t here.
Advanced Modeling The t it le of t he book m ight suggest t hat you find advanced and int erest ing exam ples of m odeling of cert ain problem areas. That 's not exact ly t he case. I nst ead, t he applicat ion focus is m ore about applying TDD and adding infrast ruct ure t o DDD.
finally{} As you can appreciat e, a book proj ect like t his is hardly ever t he work of only one person. On t he cont rary, it 's a j oint effort . See t he list of people in t he Acknowledgm ent s sect ion, and also rem em ber t hat even m ore people have been involved, especially during product ion. That said, any errors we didn't cat ch before t he book went t o print are m ine and m ine alone. I will post inform at ion of int erest t o readers of t he book at w w w .j nsk.se/ adddp. Get t ing back t o bridging gaps, t he phot o of t he Øresund Bridge was t aken by m y friend Magnus von Schenck on one of his sailing t rips. Even t hough t his book has not been as t ough t o writ e as t he first one, t here has been a fair am ount of blood, sweat , and t ears. I hope t he book m ight save you som e of t hat . Have fun and good luck! Jim m y Nilsson www.j nsk.se/ weblog/ List erby, Sw eden Sept em ber 2005
Acknowledgments Wit h t he risk of forget t ing som e people t hat should get a m ent ion here ( if so, you know who you are and a big t hanks t o you, t oo) , I say a huge t hanks t o t he following: My foreword aut hors: Mart in Fowler and Eric Evans. My guest aut hors: Frans Boum a, Dan Byst röm , Udi Dahan, Erik Dörnenburg, Mat s Helander, I ngem ar Lundberg, Philip Nelson, Claudio Perrone, Aleksandar Seovi , and Christ offer Skj oldborg. My reviewers who provided lot s of valuable feedback: William Bulley, Mark Burhop, Dan Byst röm , Russ Condick, Andy Conrad, Christ ian Crowhurst , Mike Dörfler, St eve Eichert , Eric Evans, Mart in Fowler, Paul Gielens, Chris Haddad, Kim Harding Christ ensen, Mat s Helander, Neeraj Gupt a, Anders Hessellund, Roger Johansson, Roger Krat z, Trond- Eirik Kolloen, I ngem ar Lundberg, Pat rik Löwendahl, Marcus Mac I nnes, Philip Nelson, Per- Ola Nilsson, Fredrik Norm én, Johan Norm én, Michael O'Brien, Michael Plat t , Sébast ien Ros, Mart in Rosén- Lidholm , Enrico Sabbadin, Aleksandar Seovi , Christ offer Skj oldborg, George Vish I I , Gregory Young, and Christ er Åkesson. My art ist of t he figures for t he part s int roduct ions: Kj ell Warnquist My language edit or: Lydia West My developm ent edit or: Chris Zahn My proj ect edit or: Michael Thurst on My copy edit or: Elise Walt er My m arket ing m anager: Curt Johnson My acquisit ions edit or: Karen Get t m an ( and Sondra Scot t who t alked m e int o t he proj ect in t he first place) Wit hout you t here would be no book, or at least a book of inferior qualit y. Thanks guys, I owe you!
Part I: Background I n t he " Background" part , we discuss archit ect ure and processes in general t erm s. There is a lot of em phasis on Dom ain Models and Dom ain- Driven Design ( DDD) [ Evans DDD] . We also int roduce pat t erns and Test - Driven Developm ent ( TDD) .
Th e fir st pa r t is a bou t se t t in g t h e sce n e . Rom e o a n d Ju lie t m u st h a ve a sce n e .
Chapt er 1 Values t o Value Chapt er 2 A Head St art on Pat t erns Chapt er 3 TDD and Refact oring
Chapter 1. Values to Value: Or Embarrassing Ramblings When SelfReflecting on the Last Few Years This chapt er's int ent ion is t o set t he scene. I will do t hat by looking back in t im e over t he last few years regarding how I have t hought about different concept s and how m y ideas have changed over t im e. We will j um p around quit e a lot and cover a lot of ground, but t he overall idea is t o discuss values t o value regarding archit ect ure and processes for developm ent . On t his j ourney, we will int roduce and t alk about m any concept s t hat we will discuss in dept h lat er on in t he book. St art t he cam era...Act ion!
Overall Values I n t he past , I was very good at planning ahead. I oft en added funct ionalit y, st ruct ures, and m echanism s t o m y proj ect s proact ively. That part usually t urned out pret t y well, but I oft en forgot about t he art ifact s added on t hat never cam e t o any good use. Of course, I added loads of t hem , t oo. The cost was pret t y large, bot h in developm ent t im e and in increased com plexit y. Over t he last few years, we've been encouraged t o use anot her approach: " Do t he sim plest t hing t hat could possibly work." To a large ext ent , t he idea com es from t he Ext rem e Program m ing ( XP) m ovem ent [ Beck XP] . Anot her fairly sim ilar way t o put it is " You Aren't Going t o Need I t " ( YAGNI ) , which is a good way of helping you st ay in line. I guess " Keep I t Sim ple St upid" ( KI SS) could go here, t oo. Bot h approaches are kind of t he t wo ext rem es ( add all you can t hink of up front versus do t he sim plest t hing) , but I t hink t hey bot h m iss som et hing in t hat t hey don't address t he t radeoffs t hey m ake. Just about every quest ion regarding " is t his or t hat best " can be answered wit h " it depends." I t 's about t radeoffs. I t end t o prefer an approach t hat is som ewhere in t he m iddle, m oving in one or t he ot her direct ion depending upon t he sit uat ion. The word " lagom " is a Swedish word m eaning som et hing like " j ust right " or " not t oo m uch, not t oo lit t le." Lagom or " t o balance" t oget her wit h being cont ext sensit ive are t he overall values I 'd like t o value, as well as cont inuous learning. Let 's have a closer look at a couple of m ore specific areas of values ( archit ect ure and process ingredient s) , st art ing wit h som e aim ed at archit ect ure t o get us int o t he right m ood.
Architecture Styles to Value Archit ect s and developers m ust m ake design decisions based on t he requirem ent s of t he proj ect . I haven't collect ed an exhaust ive list of values regarding archit ect ure st yles here, but I have decided on a few m ain t hings I 'd like t o com m ent on t o som e ext ent , such as m odel focus, dom ain m odels, dat abases, dist ribut ion, and m essaging. First , I t hink it 's wise t o keep a m odel focus.
Focus on the Model For a long t im e, I have liked t he obj ect - orient ed paradigm a lot , but it wasn't unt il pret t y recent ly t hat I m ade t he full m ove t o t hat paradigm m yself. There have been plat form problem s wit h using t he paradigm before, but now t he plat form s are m at ure.
N ot e As a reviewer point ed out , m at urit y depends on t he plat form we are t alking about . I f you com e from a VB background, what was j ust said is reasonable, but if you com e from Java, Sm allTalk, C# , and so on, t he plat form has been m at ure for quit e som e t im e.
Around 13 years ago, I t ried t o use a visual m odel t o com m unicat e m y underst anding of t he requirem ent s of a syst em I was about t o build, and I used som e OMT [ Rum baugh OMT] sket ches. ( OMT st ands for Obj ect Modeling Technique and was a m et hod wit h a process and a not at ion. The not at ion was very sim ilar t o t he one in Unified Modeling Language, UML, which isn't j ust a coincidence because OMT was t he m ain inspirat ion for t he not at ion in UML.) We were discussing m ult iplicit y bet ween classes, where behavior should belong, and so on. I realized aft er a few sessions t hat using m y t echnique for discussion wit h expert users, inst ead of t heir t echnique, was a com plet e failure. They answered m y quest ions random ly and didn't see or add m uch value at all in t he discussion. ( The syst em at large wasn't a failure, it 's st ill being used and it 's t he core syst em t here, but t he developm ent process didn't go as sm oot hly as it could have. I had t o change m et hod.)
Use Case Focus I have t hought about t hat experience several t im es and have act ually j oked about how naïve I was. I n t he years following, I always t ried t o play by t he m et hodologies of t he t arget groups; I m ean t he m et hodologies of t he users wit h t he users, and t he m et hodologies of soft ware developm ent wit h developers. One t echnique t hat worked pret t y well in bot h cam ps was t he use case t echnique [ Jacobson OOSE] . ( At least it worked out well in t he inform al m anner of XP [ Beck XP] st ories, short t ext descript ions, which was what I used. Such a descript ion is a short descript ion of a piece of funct ionalit y in a syst em . An exam ple is " Regist er Order for Com pany Cust om er." ) I t was very nat ural t o users and could be m ade nat ural t o developers, t oo, so it becam e t he bridging t ool of m ine for years. The way I did t he bridging was t o have one class per use case in t he soft ware.
I t did com e t o m y at t ent ion t hat t hanks t o m y way of applying use cases, I becam e pret t y procedural in m y t hinking. I was designing a lit t le bit like Transact ion Script [ Fowler PoEAA] , but I t ried t o balance it by generalizing as m uch behavior as possible ( or at least suit able) . A few years ago I heard I var Jacobson t alking about use cases, and I was pret t y surprised when I realized t hat he didn't encapsulat e t he use cases in classes of t heir own as I had expect ed and had done for a long t im e. Anot her t hing t hat got m e kind of worried about t his m et hod was t he const ant st ruggle I was having wit h m y friend and Valhalla- developer Christ offer Skj oldborg when we worked wit h t he Dom ain Model pat t ern [ Fowler PoEAA] . He saw lit t le value in t he use case classes, m aint aining t hat t hey could even get in t he way because t hey m ight becom e a hindrance when m ixing and m at ching use case part s.
Different Patterns for Dealing with the Main Logic Before we cont inue, I m ust say a few words about Transact ion Script and Dom ain Model, which we have already t ouched upon. Mart in Fowler discusses t hree ways of st ruct uring t he m ain logic in applicat ion archit ect ure in his book Pat t erns of Ent erprise Applicat ion Archit ect ure [ Fowler PoEAA] . Those are Transact ion Script , Table Module, and Dom ain Model. Transact ion Script is sim ilar t o bat ch program s in t hat all funct ionalit y is described from st art t ill end in a m et hod. Transact ion Script is very sim ple and useful for sim ple problem s, but breaks down when used for dealing wit h high com plexit y. Duplicat ion will be hard t o avoid. St ill, very m any very large syst em s are built t his way. Been t here, done t hat , and I 've seen t he evidence of duplicat ion even t hough we t ried hard t o reduce it . I t crops up. Table Module encapsulat es a Recordset [ Fowler PoEAA] , and t hen you call m et hods on t he Table Module for get t ing inform at ion about t hat cust om er wit h id 42. To get t he nam e, you call a m et hod and send id 42 as a param et er. The Table Module uses a Recordset int ernally for answering t he request . This cert ainly has it s st rengt hs, especially in environm ent s when you have decent im plem ent at ions for t he Recordset pat t ern. One problem , t hough, is t hat it also has a t endency t o int roduce duplicat ion bet ween different Table Modules. Anot her drawback is t hat you t ypically can't use polym orphist ic solut ions t o problem s because t he consum er won't see t he obj ect ident it ies at all, only valuebased ident it ies. I t 's a bit like using t he relat ional m odel inst ead of t he obj ect - orient ed m odel. Dom ain Model inst ead uses obj ect orient at ion for describing t he m odel as close t o t he chosen abst ract ions of t he dom ain as possible. I t shines when dealing wit h com plexit y, bot h because it m akes usage of t he full power of obj ect orient at ion possible and because it is easier t o be t rue t o t he dom ain. Usage of t he Dom ain Model pat t ern isn't wit hout problem s, of course. A t ypical one is t he st eep learning curve for being able t o use it effect ively.
Domain-Driven Design Focus The real eye- opener for m e regarding t he usage of use cases was Dom ain- Driven Design ( DDD) [ Evans DDD] . ( We will t alk a lot about DDD soon, but for now, let 's j ust say it 's about focusing on t he
dom ain and let t ing it affect t he soft ware very m uch.) I st ill t hink t hat t he use case t echnique is a great way of com m unicat ing wit h users, but DDD has m ade m e t hink it can help a lot if we can m anage t o act ively involve t he users in discussions about t he core m odel. I t can head off m ist akes very early and help t he developers underst and t he dom ain bet t er.
N ot e " Model" is one of t hose ext rem ely overloaded t erm s in soft ware. I t hink we all have an int uit ive idea about what it is, but I 'd like t o describe how I t hink about it . I nst ead of t rying t o com e up wit h t he definit ion, here in all sim plicit y ( t his could fill a book of it s own by t he right aut hor) are a few propert ies of a m odel t hat you can com pare t o your own underst anding of t he t erm . A m odel is Part ial For a cert ain purpose A syst em of abst ract ions A cognit ive t ool Also, A m odel has several present at ions ( for exam ple: language, code, and diagram s) . There are several m odels in play in a syst em . I f you would like t o read m ore, very m uch has been writ t en about what a m odel is. A good place t o st art is t he first pages in Eric Evans' book [ Evans DDD] . ( A special t hanks t o Anders Hessellund and Eric Evans for inspirat ion.)
The m odel is a great t ool for com m unicat ion bet ween developers and users, and t he bet t er t he com m unicat ion is bet ween t hose groups, t he bet t er t he soft ware will becom e, bot h in t he short and t he long run. We ( including m e) have been using m odels forever, of course, but t he difference bet ween one of m y old m odels and t hose t hat have been built wit h t he ideas of DDD is t hat m y old m odels had m uch m ore infrast ruct ure focus and t echnical concept focus. My new m odels are m uch cleaner from such dist ract ions and inst ead focus t ot ally on t he core dom ain, it s m ain concept s, and t he dom ain problem s at hand. This is a big m ind shift . Anot her way of expressing it is t hat t echnicalit ies ( such as user int erface fashion) com e and go. Core business last s. And when t he core business changes, we want t o change t he m odel and t he soft ware. I t 's not rocket science so far, and I 'm probably j ust kicking in open doors. I t hink, however, t his lies at t he very core of how t o achieve efficient soft ware developm ent , and in m y experience it is rarely used. By t his I m ean focusing on t he m odel, having a m odel ( wit h different present at ions) t hat is used by bot h users and developers, and not get t ing dist ract ed by unim port ant det ails.
Why Model Focus?
I 'd like t o exaggerat e t his a bit . Let 's play a gam e: What kind of developer would be t he best one for building a vert ical syst em in a special field, let 's say in finance? I t goes wit hout saying t hat developer should be t echnically savvy, very skilled and experienced, have a huge social net work, and so on. I t would also be very nice if he was ext rem ely skilled in t he business dom ain it self and it wouldn't hurt if he had worked as, say, a t rader for t en years, if t hat 's t he kind of syst em t o be built . Finding developers like t hat does happen, but in m y experience it 's m ore t he except ion t han t he rule. I n t he case of t he developer being able t o m ove on t o anot her dom ain for a new syst em when t he financial syst em is up and running, t hat except ion becom es even m ore rare. This is because developers j ust can't have t en years of experience in logist ics, healt h care, insurance, and t he rest . Anot her solut ion can be t o let t he dom ain expert users develop t he soft ware t hem selves. This has been an old dream for decades, and t o a degree it 's slowly becom ing m ore and m ore viable all t he t im e. At t he sam e t im e, it t akes away t im e from t hem , t im e t hey oft en can use bet t er for t heir core work. There's also t oo m any t echnical problem s st ill involved. So what 's t he next best t hing? The answer is obvious ( at least from t oday's st andpoint ) : m ake t he developer learn as m uch as possible about t he dom ain he or she is working on and add users t o t he pict ure t o bring t he dom ain expert ise t o t he proj ect t eam and t o act ively and const ruct ively work wit hin t he proj ect . The users would not j ust set t he requirem ent salt hough t hat is also very im port ant but would act ually help out wit h designing t he core of t he syst em . I f we can m anage t o creat e t his at m osphere, nobody is m ore skilled t han t he dom ain expert user at deciding on what t he core is, what t he key abst ract ions are, and so on. Of course, as developers we are also necessary. The whole t hing is done in cooperat ion. For exam ple, what m ight be seen as a t iny det ail t o businesspeople can be a huge t hing t o us in t he dom ain m odel. They m ight say: " Oh, som et im es it 's like t his inst ead," and everyt hing is t ot ally different , but because it 's not t he m ost com m on variant , t hey t hink it 's unim port ant .
Time to Try Discussing the Model with Customers Again Why would I now succeed in discussing t he m odel wit h t he cust om ers when I failed before? Many t hings have changed. First , it 's im port ant t o have a sense of t he cont ext and t he proj ect group. Second, use cases can help a lot when get t ing st art ed, and t hen you can swit ch t he focus lat er t o t he core m odel it self. Third, building syst em s is som et hing m any m ore people have experience in and have been t aught about now. Fourt h, t o be picky, t he m odel represent ed as graphical, UML- like sket ch for exam ple is not t he m ost im port ant t hing t o have users work on; it 's st ill t ypically a represent at ion t hat developers like bet t er t han users. What we are looking for is t he ubiquit ous language [ Evans DDD] . The ubiquit ous language is not som et hing like UML, XML Schem a, or C# ; it 's a nat ural, but very dist illed, language for t he dom ain. I t 's a language t hat we share wit h t he users for describing t he problem at hand and t he dom ain m odel. I nst ead of us list ening t o t he users and t rying t o t ranslat e it t o our own words, a ubiquit ous language would creat e fewer reasons for m isunderst andings, and it will becom e easier for t he users t o underst and our sket ches and act ually t o help correct m ist akes, helping us gain new knowledge about t he dom ain. I f it 's suit able, you can discuss ubiquit ous language in t he cont ext of t he graphical/ code m odels wit h t he users. I f not , you st ill have t he m ost im port ant t hing done if you cat ch t he ubiquit ous language.
N ot e Eric Evans com m ent ed on t he previous wit h t he following: " Som et hing t o m ake clear is t hat
t he ubiquit ous language isn't j ust t he dom ain expert 's current lingo. That has t oo m any am biguit ies and assum pt ions and also probably t oo large a scope. The ubiquit ous language evolves as a collaborat ion bet ween t he dom ain expert s and t he soft ware expert s. ( Of course, it will resem ble a subset of t he dom ain j argon.) "
The ubiquit ous language is som et hing t hat you should work hard t o keep well- defined and in synch wit h t he soft ware. For exam ple, a change in t he ubiquit ous should lead t o a change in t he soft ware and vice versa. Bot h art ifact s should influence each ot hers.
If You Have a Model Focus, Use the Domain Model Pattern I f we have agreed on having a deep m odel focus and using obj ect - orient at ion, I believe t he nat ural result is t o use t he Dom ain Model pat t ern [ Fowler PoEAA] for st ruct uring t he core logic of t he applicat ions and services. You find an exam ple of a sm all Dom ain Model in Figure 1- 1 .
Figu r e 1 - 1 . An e x a m ple of a D om a in M ode l, a n e a r ly sk e t ch for t h e e x a m ple a pplica t ion , fu r t h e r discu sse d in Ch a pt e r 4 , " A N e w D e fa u lt Ar chit e ct ur e "
N ot e I f you aren't up t o speed on UML, I t hink a good book t o read is UML Dist illed [ Fowler UML Dist illed] . I t 's not essent ial t hat you underst and UML, but I t hink it will be helpful t o know
t he bones because I will be using UML as a sket ch t ool here and t here ( of course, you will benefit from it at ot her t im es as well, not j ust wit h t his book) .
Even t hough Figure 1- 1 shows j ust an early sket ch of a sm all dom ain m odel, I t hink we can see t hat t he m odel expresses dom ain concept s and not t echnical dist ract ions. We can also see t hat it cont ains several cooperat ing sm all pieces, which t oget her form a whole.
N ot e I f you wonder why Figure 1- 1 is handwrit t en, it is t o st ress t he point t hat it is a sket ch. We will explore and develop t he det ails in codefor exam ple, wit h t he help of Test - Driven Developm ent ( TDD) . ( We will discuss TDD lat er in t his chapt er, as well as in Chapt er 3, " TDD and Refact oring." )
Before we t alk m ore about t he specific st yle I used for t he Dom ain Model, I t hink it 's t im e t o look back again.
Domain Model History from My Rear View Mirror I st ill rem em ber t he first t im e I t ried t o use t he Dom ain Model, even t hough I didn't call it t hat at t he t im e. I t was around 1991, and I couldn't underst and how it should m at ch up in t he UI . I don't rem em ber what t he applicat ion was, but I rem em ber t hat I t ried t o decide on what t he m enus should look like in order t o be obj ect - orient ed. I didn't com e over t hat first hurdle t hat t im e. I t hink I was looking for a very close m apping bet ween t he UI and t he Dom ain Model and couldn't find out how t o achieve it . A few years prior t o t hat , I worked on an applicat ion t hat was said t o use obj ect - orient ed ideas, for exam ple, for t he UI . There were no big breakt hroughs, perhaps, but it m anaged t o achieve sm all t hings. For exam ple, inst ead of first choosing a funct ion and t hen deciding t o what obj ect ( such as a port folio) t o apply it , t he user first chose t he obj ect and t hen decided bet ween t he different funct ions t hat were possible for t he chosen obj ect . At t he t im e t his was pret t y different from how m ost business applicat ions were navigat ed in t he UI .
A Few Words About Naked Objects Perhaps I wasn't t ot ally off wit h m y int ent ions. Perhaps what I was t rying t o achieve was som et hing like Naked Obj ect s [ Pawson/ Mat t hews Naked Obj ect s] , but I really didn't get very far wit h it . Wit hout going int o det ails about Naked Obj ect s, I t hink a short explanat ion is in order. The basic idea is t hat not only developers but users also like obj ect - orient at ion. They like it even m ore t han we norm ally t hink, and t hey would quit e oft en be happy wit h a UI t hat is very close t o t he Dom ain Model it self, so close t hat t he UI can be creat ed aut om at ically by a fram ework direct ly based on t he Dom ain Model. A syst em based on naked obj ect s aut om at ically present s a form t o a user t hat cont ains widget s exposing t he propert ies in t he Dom ain Model class. For m ore com plex t asks, t here is som e need for cust om izat ion ( as always) , but t he idea is t o cut down on t hat as well, again t hanks t o t he fram ework t hat underst ands what UI t o creat e from t he Dom ain Model fragm ent s. So when t he Dom ain Model is " done," t he UI is m ore or less done as well. For t he m om ent I don't have first - hand experience wit h t he concept and t echnique, but an appealing t wist t o m e of t he idea is t hat t he users will really get a chance t o see and feel t he m odel, which should be very helpful in bridging t he gap bet ween developers and users.
Aft er t hat , I t ried Dom ain Model approaches m any t im es over t he years. I found problem s wit h using it , especially relat ed t o perform ance overhead ( especially in dist ribut ed scenarios) . That said, som e of m y real world applicat ions were built in t hat st yle, especially t he sim pler ones. What is also im port ant t o say is t hat however m uch I want ed t o build very powerful, well- designed Dom ain Models, t hey didn't t urn out as I had ant icipat ed. That wasn't t he only problem I had, t hough....
Old Truths Might Be Wrong I 've believed in Dom ain Models and t ried t o use t hem several t im es in t he past , but m ost at t em pt s t o im plem ent it in obj ect - orient ed fashion led m e t o t he conclusion t hat it doesn't work. That was, for exam ple, t he case wit h m y COM+ applicat ions in Visual Basic 6 ( VB6) . The m ain problem was t hat perform ance overhead was t oo high. When I wrot e m y previous book [ Nilsson NED] , which described a default - archit ect ure for .NET applicat ions, I reused m y old VB6/ COM+ knowledge and didn't m ake t he discussed archit ect ure Dom ain Model- based at all. Lat er on I st art ed up new experim ent s again, t rying t o see if I could get down t o som et hing like 10% in response- t im e overhead on com m on scenarios ( such as fet ching a single order, fet ching a list of orders for a cust om er, or saving an order) when using Dom ain Model in .NET com pared wit h Transact ion Script s and Recordset . To m y surprise, I could get low er overhead. The old t rut hs had becom e wrong. The Dom ain Model- based archit ect ure is very m uch m ore possible now in .NET t han in VB6. One reason for t his is t hat t he inst ant iat ion t im e is reduced by orders of m agnit ude. Therefore, t he overhead is j ust m uch lower when you have very m any inst ances. Anot her reason is
t hat it 's t rivial t o writ e Marshal By Value ( so t hat not only is t he reference t o t he inst ance m oved, but t he whole inst ance, at least concept ually, is m oved) com ponent s in .NET. I t was not sim ple in t he COM world. ( I t wasn't even possible t o do it in t he ordinary sense in VB6.)
N ot e The im port ance of Marshal By Value is of less im port ance because we now m ost oft en prefer t o not send t he Dom ain Model as- is over t he wire, but m y early t est s were using t hat .
Yet anot her reason is t hat .NET bet t er support s obj ect - orient at ion; it 's j ust a m uch bet t er t oolbox.
N ot e Also wort h m ent ioning is t hat t here hasn't been m uch em phasis on t he Dom ain Model in t he Microsoft com m unit y in t he past . The Java com m unit y, on t he ot her hand, is alm ost t he opposit e. I rem em ber when I went t o a workshop wit h m ost ly Java people. I asked t hem about what t heir favorit e st ruct ure for t he logic was, perhaps if t hey used Recordset ( added in JDBC version 3) a lot . They looked at m e funnily as if t hey hadn't underst ood t he quest ion. I quickly realized t hat Dom ain Model was m ore or less t he de fact o st andard in t heir case.
To set all t his st raight , I 'd like t o clarify t wo t hings. First , I do not say t hat you should choose t he Dom ain Model pat t ern because of perform ance reasons. Rat her t hat you oft en can choose t he Dom ain Model pat t ern wit hout get t ing perform ance problem s. Second, I do not say t hat t o be able t o use a m odel focus, you need a cert ain t echnology. Different t echnologies are m ore or less appropriat e for expressing t he m odel in soft ware in close resem blance wit h t he dom ain. Of course, t here is not a single t echnology t hat is always best . Different dom ains and different problem s set different fact ors for what is an appropriat e t echnology. So I see t echnology as an enabler. Different t echnologies can be bet t er enablers t han ot hers. To sum m arize t his, it 's very m uch a m at t er of design if t he Dom ain Model pat t ern can be used efficient ly enough or not . Som e good news regarding t his is t hat t here is lot s of good inform at ion t o gain. Dom ain- Driven Design and it s st yle for st ruct uring Dom ain Models provides lot s of valuable help, and t hat is som et hing we will spend a lot of t im e t rying out in t he book.
One Root Structure So if we choose t o focus on a Dom ain Model, it m eans t hat we will get all t he people on t he proj ect t o buy int o t he m odel. This goes for t he developers, of course. I t m ight even be t hat t he DBAs can agree on seeing t he Dom ain Model as t he root , even t hough t he dat abase design will be slight ly different . I f so, we have probably accom plished get t ing t he DBA t o
speak t he ubiquit ous language, t oo! ( As a m at t er of fact , t he m ore you can get your DBA t o like t he Dom ain Model and adhere t o it , t he easier it will be t o im plem ent it . This is because you will need t o creat e less m apping code if t he dat abase design isn't radically different from t he Dom ain Model, but m ore like a different view of t he sam e m odel.) Oh, and even t he users could buy int o t he Dom ain Model. Sure, different st akeholders will have different needs from t he Dom ain Model, different views regarding t he det ail level, for exam ple, but it 's st ill one root st ruct urea st ruct ure t o live wit h, grow wit h, change.... As you will find, I 'm going t o put a lot of energy int o discussing Dom ain Model- based solut ions in t his book from now on, but before doing t hat , I 'd like t o t alk a bit about ot her archit ect ure values. Let 's leave t he dom ain focus for a while and discuss som e m ore t echnically focused areas. First is how t o deal wit h t he dat abase.
Handle the Database with Care I n t he past , I 've t hought a lot about perform ance when building syst em s. For exam ple, I used t o writ e all dat abase access as handwrit t en, st ored procedures. Doing t his is usually highly effect ive perform ance- wise during runt im e, especially if you don't j ust have CRUD ( Creat e Read Updat e Delet e) behavior for one inst ancesuch as a single cust om erat a t im e, but have " sm art er" st ored procedures; for exam ple, doing several operat ions and affect ing m any rows in each call.
The Right Efficiency Even t hough hardware capabilit y is probably st ill growing in accordance wit h som et hing like Moore's law, t he problem of perform ance is an everyday one. At t he sam e t im e as t he hardware capabilit ies increase, t he size of t he problem s we t ry t o solve wit h soft ware also increases. I n m y experience, perform ance problem s are m ore oft en t han not t he result of bad dat abase access code, bad dat abase st ruct ure, or ot her such t hings. One com m on reason for all t his is t hat no effort at all has been spent on t uning t he dat abase, only on obj ect - orient at ion purit y. This in it s t urn has led t o an ext rem e num ber of roundt rips, inefficient and even incorrect t ransact ion code, bad indexing schem es, and so on. To m ake it a bit m ore concret e, let 's look at an exam ple where obj ect orient at ion has been t hought of as im port ant while dat abase handling has not . Let 's assum e you have a Customer class wit h a list of Orders. Each Order inst ance has a list of OrderLine inst ances. I t looks som et hing like Figure 1- 2 .
Figu r e 1 - 2 . UM L dia gr a m for Cu st om e r / Or de r e x a m ple
Moreover, let 's assum e t hat t he m odel is very im port ant t o you and you don't care very m uch about how t he dat a is fet ched from and st ored t o t he relat ional dat abase t hat is being used. Here, a possible ( yet naïve) schem a would be t o let each class ( Customer , Order and OrderLine) be responsible for persist ing/ depersist ing it self. That could be done by adding a Layer Supert ype [ Fowler PoEAA] class ( called PersistentObject in t he exam ple shown in Figure 1- 3 ) which im plem ent s, for exam ple, GetById() and from which t he Dom ain Model classes inherit ( see Figure 1- 3 ) .
Figu r e 1 - 3 . UM L dia gr a m for Customer/Order e x a m ple , w it h La ye r Su pe r t ype
Now, let 's assum e t hat you need t o fet ch a Customer and all it s Orders, and for each order, all OrderLines. Then you could use code like t his: //A consumer Customer c = new Customer(); c.GetById(id);
N ot e I f you wonder about //A consumer in t he previous code snippet , t he idea is t o show t he class nam e ( and som et im es m et hods) for code snippet s like t hat t o increase clarit y. Som et im es t he specific class nam e ( as in t his case) isn't im port ant , and t hen I use a m ore generic nam e inst ead. C+ + kind of has t his concept built in because m et hod nam es are writ t en like ClassName::MethodName, but I t hink som e sm all com m ent s should provide t he sam e effect .
What happens now is t hat t he following SQL st at em ent s will execut e: SELECT CustomerName, PhoneNumber, ... FROM Customers WHERE Id = 42
Next t he customer's GetById() m et hod will fet ch a list of orders, but only t he keys will be fet ched by calling GetIdsOfChildren() , which will execut e som et hing like t his: SELECT Id FROM Orders WHERE CustomerId = 42
Aft er t hat , t he Customer will inst ant iat e Order aft er Order by it erat ing over t he DataReader for Order ident ifiers, delegat ing t he real work t o t he GetById() m et hod of t he Order like t his: //Customer.GetById() ... Order o; while theReader.Read() { o = new Order(); o.GetById(theReader.GetInt32(0)); c.AddOrder(o); }
Then for each Order it 's t im e t o fet ch t he ident ifiers of all t he OrderLines...well, you get t he pict ure. What happened here was t hat t he obj ect perspect ive was used t o a cert ain ext ent ( at least t hat was t he int ent ion of t he designer) inst ead of t hinking in set s, as is what relat ional dat abases are based
on. Because of t hat , t he num ber of roundt rips t o t he dat abase was ext rem ely high ( one for every row t o fet ch plus a few m ore) . The efficiency plum m et ed t hrough t he floor.
N ot e Wort h m ent ioning is t hat t he behavior j ust described m ight be exact ly what you need t o avoid m assive loading of dat a in a specific scenario. I t 's hard t o point out som et hing t hat is always bad. Rem em ber t he cont ext .
On t he ot her hand, if we t hink about t he ot her ext rem e, handwrit t en and hand opt im ized st ored procedures, it could look like t his: CREATE PROCEDURE GetCustomerAndOrders(@customerId INT) AS SELECT Name, PhoneNumber, ... FROM Customers WHERE Id = @customerId SELECT Id, ... FROM Orders WHERE CustomerId = @customerId SELECT Id, ... FROM OrderLines WHERE OrderId IN (SELECT Id FROM Orders WHERE CustomerId = @customerId)
They are oft en efficient ( as I said) during runt im e. They are very inefficient during m aint enance. They will give you lot s of code t o m aint ain by hand. What 's m ore, st ored procedures in Transact SQL ( T- SQL, t he SQL dialect used for Sybase SQL Server and Microsoft SQL Server) , for exam ple, don't lend t hem selves well t o ordinary t echniques for avoiding code duplicat ion, so t here will be quit e a lot of duplicat e code.
N ot e I know, som e of m y readers will now say t hat t he previous exam ple could be solved very efficient ly wit hout st ored procedures or t hat t hat st ored procedure wasn't t he m ost efficient one in all circum st ances. I 'm j ust t rying t o point out t wo ext rem e exam ples from an efficiency point of view. I m ean badly designed codeat least from an efficiency point of viewt hat uses dynam ic SQL com pared t o bet t er design and well- writ t en st ored procedures.
So t he quest ion is if runt im e efficiency is t he m ost im port ant fact or for choosing how t o design?
Maintainability Focus I f I only have t o choose one " abilit y" as t he m ost im port ant one, t hese days I would choose m aint ainabilit y. Not t hat it 's t he only one you need, it absolut ely is not , but I t hink it 's oft en m ore im port ant t han scalabilit y, for exam ple. Wit h good m aint ainabilit y, you can achieve t he ot her abilit ies easily and cost - effect ively. Sure, t his is a huge sim plificat ion, but it m akes an im port ant point . Anot her way t o see it is t o com pare t he t ot al cost of producing a new syst em wit h t he t ot al cost of t he m aint enance of t he syst em during it s ent ire life cycle. I n m y experience, t he m aint enance cost will be m uch great er for m ost successful syst em s. To conclude t his, m y current belief is t hat it is wort h giving som e at t ent ion t o t he dat abase. You should see it as your friend and not your enem y. At t he sam e t im e, however, hand writ ing all t he code t hat deals wit h t he dat abase is m ost oft en not t he " right " approach. I t has lit t le or not hing t o do wit h t he m odel t hat I want t o focus on. To use a com m on quot e by Donald Knut h, " Prem at ure opt im izat ion is t he root of all evil." I t 's bet t er t o avoid opt im izat ions unt il you have m et rics saying t hat you have t o do t hem . To m ake a quick and long j um p here, I t hink t hat decent design, t oget her wit h Obj ect Relat ional Mapping ( O/ R Mapping) , is oft en good enough, and when it isn't , you should hand t une. O/ R Mapping is a bit like t he opt im izer of dat abase serversm ost of t he t im e it is sm art enough, but t here are sit uat ions when you m ight need t o give it a hand.
N ot e Decent design and O/ R Mapping will be discussed a lot t hroughout t he book. For now, let 's define O/ R Mapping as a t echnique for bridging bet ween obj ect orient at ion and relat ional dat abases. You describe t he relat ionship bet ween your obj ect - orient ed m odel and your relat ional dat abase and t he O/ R Mapper does t he rest for you.
So one approach is t o use t ools such as O/ R Mappers for m ost of t he dat abase access code and t hen hand writ e t he code t hat needs t o be handwrit t en for t he sake of execut ion efficiency. Let 's t ake a closer look at t he problem som et hing like an O/ R Mapper will have t o deal wit h, nam ely m apping bet ween t wo different worlds, t he obj ect - orient ed and t he relat ional.
The Impedance Mismatch Between Domain Model and Relational Database I m ent ioned previously t hat m apping t o t he UI is one problem when using Dom ain Models. Anot her well- known problem is m apping t o relat ional dat abases. That problem is com m only referred t o as im pedance m ism at ch bet ween t he t wo worlds of relat ional and obj ect - orient at ion. I 'd like t o give m y t hought s on what t hat im pedance m ism at ch is, alt hough it is som et hing of a pop version. For furt her and m ore form al inform at ion see Cat t el [ Cat t ell ODM] . First of all, t here are t wo t ype syst em s if you use bot h a relat ional dat abase and an obj ect - orient ed m odel. One part of t he problem is caused by t he fact t hat t he t ype syst em s are in different address spaces ( even if not on different m achines) , so you have t o m ove dat a bet ween t hem .
Secondly, not even prim it ive t ypes are exact ly t he sam e. For exam ple a string in .NET is of variable lengt h, but in Microsoft SQL Server a st ring is t ypically a varchar or a char or text. I f you use varchar/char, you have t o decide on a m axim um widt h. I f you use text, t he program m odel is t ot ally different t han for t he ot her st ring t ypes in SQL Server. Anot her exam ple is DateTime . DateTime in .NET is pret t y sim ilar t o SQL Server, but t here are differences. For inst ance, t he precision is down t o 100 nanoseconds for .NET, but " only" down t o 3/ 1000 of a second in SQL Server. Anot her " fun" difference is if you set a DateTime in .NET t o DateTime.MinValue it will lead t o an except ion if you t ry t o st ore it in a SQL Server DateTime . Yet anot her difference is t hat of nullabilit y. You can't st ore null in an ordinary int in .NET, but t hat 's perfect ly valid in SQL Server.
N ot e The problem s m ent ioned so far exist whet her you use a Dom ain Model or not .
A big difference is how relat ionships are dealt wit h. I n a relat ional dat abase, relat ionships are form ed by duplicat ed values. The prim ary key of t he parent ( for exam ple, Customers.Id) is duplicat ed as a foreign key in t he children ( for exam ple, Orders.CustomerId) , effect ively let t ing t he child rows " point " t o t heir parent s. So everyt hing in a relat ional m odel is dat a, even t he relat ionships. I n an obj ect orient ed m odel, relat ionships can be set up in m any different ways ( for exam ple, via values sim ilar t o t hose in t he relat ional, but t hat is not t ypical) . The m ost t ypical solut ion is t o use t he built - in obj ect ident ifiers let t ing t he parent have references t o obj ect ident ifiers of it s children. This is, as you can see, a com plet ely different m odel. Navigat ion in a relat ional m odel can be done in t wo ways. First , one can use a parent prim ary key and t hen use a query t o find all children t hat have foreign keys wit h t he sam e value as t he prim ary key of t he parent . Then, for each child, t he child prim ary key can be used wit h a new query t o ask for all t he children of t he child, and so on. The ot her and probably m ore t ypical way of navigat ing in t he relat ional m odel is t o use relat ional j oins bet ween t he parent set and t he children set . I n an obj ect orient ed m odel, t he t ypical way of navigat ing is t o sim ply t raverse t he relat ionships bet ween inst ances. Next you find t wo code snippet s where I h av e an order wit h ordernum ber 42. Now I want t o see what t he nam e of t he cust om er for t hat order is. C# : anOrder.Customer.Name
SQL: SELECT Name FROM Customers WHERE Id IN (SELECT CustomerId FROM Orders WHERE OrderNumber = 42)
N ot e I could j ust as well have used a JOI N for t he SQL case, of course, but I t hink a subquery was clearer here.
Anot her navigat ion- relat ed difference is t hat for obj ect s, t he navigat ion is oneway. I f you need bidirect ional navigat ion it is act ually done wit h t wo separat e m echanism s. I n t he relat ional m odel, t he navigat ion is always bidirect ional.
N ot e The quest ion about direct ionalit y can be seen as t he opposit e also t o what I j ust explained, because in t he relat ional dat abase, t here is j ust a " point er" in one direct ion. I st ill t hink t hat " point er" is bidirect ional because you can use it for t raversal in bot h direct ions, and t hat 's what I t hink is im port ant .
As we have already t ouched upon, t he relat ional m odel is set - based. Every operat ion deals wit h set s. ( A set can be j ust one row, of course, but it is st ill a set .) However, in t he obj ect - orient ed m odel, we deal wit h one obj ect at a t im e inst ead. Moreover, t he dat a in t he relat ional m odel is " global," while we st rive hard t o m aint ain privacy of t he dat a in an obj ect - orient ed m odel. When it com es t o design, t he granularit y is quit e different . Let 's t ake an exam ple t o m ake t his clear. Assum e t hat we are int erest ed in keeping t rack of one hom e phone num ber and one work phone num ber for cert ain people. I n a relat ional m odel it is norm ally alright t o have one t able called People ( plural is t he de fact o nam e st andardat least am ong dat abase peoplet o m ake it obvious we are dealing wit h a set ) .
N ot e I f I 'm picky, I should use t he word " relat ion" inst ead of " t able" when I 'm t alking about t he r elat ional m odel.
The t able has t hree colum ns ( probably m ore, but so far we have only t alked about t he phone num bers) represent ing one prim ary key and t wo phone num bers. Perhaps t here could be five colum ns, because we m ight want t o break down t he phone num bers int o t wo colum ns each for area code and local num ber, or seven if we add count ry code as well. See Figure 1- 4 for a com parison.
Figu r e 1 - 4 . Th e sa m e m ode l e x pr e sse d a s r e la t ion a l a n d obj e ct - or ie n t e d
What 's im port ant here is t hat even for 1: 1, all colum ns are norm ally defined in one t able. I n an obj ect - orient ed m odel, it would be usual t o creat e t wo classes, one called Person and one called PhoneNumber. Then a Person inst ance would be a com posit ion of t wo PhoneNumber inst ances. We could do a sim ilar t hing in t he relat ional m odel, but it would not usually m ake any sense. We t ry not t o reuse definit ions a lot in t he relat ional m odel, especially because we don't have behavior t ied t o t he t able definit ions. I t 's j ust t he opposit e in t he obj ect - orient ed m odel. Anot her way t o say t his is t hat in t he relat ional m odel, we don't increase t he sat isfied norm al form if we m ove PhoneNumbers out int o a separat e t able. We have probably j ust increased t he overhead. What it com es down t o is t hat t he relat ional m odel is for dealing wit h t abular, prim it ive dat a, and t his is bot h good and bad. The obj ect orient ed m odel deals neat ly wit h com plex dat a as well.
N ot e The relat ional m odel has a pret t y st rong concept when it com es t o definit ion reuse, which is called dom ain. Unfort unat ely, t he support for t hat concept st ill isn't very st rong in t oday's pr oduct s. I t 's also t he case t hat m any of t he product s have support for com plex dat at ypes, but it 's st ill in it s infancy.
We j ust discussed one exam ple of granularit y where a relat ional m odel is m ore coarse- grained t han an obj ect - orient ed m odel. We could also look at it t he ot her way around. For exam ple, in a relat ional m odel an order m ight have m any orderLines, but t he orderLines are on t heir own, so t o speak. Each orderLine is j ust a row; each order is ot her such rows. There is a relat ionship bet ween t hem , but t he rows are t he unit s. I n an obj ect - orient ed m odel, it m ight be a good solut ion t o see t he order as t he unit and let it be a com posit ion of orderLines. This t im e t he relat ional m odel was finer- grained t han t he obj ect - orient ed m odel.
N ot e I 'm not im plying t hat t here won't be an OrderLine class in t he Dom ain Model. There should
be. What I 'm saying is t hat what I ask for and work wit h as t he unit is an order, and orderLines is part of t he order.
Last but not least , t he relat ional m odel doesn't support inherit ance ( again, at least it 's not m ainst ream in t he m ost popular product s) . I nherit ance is at t he core of t he obj ect - orient ed m odel. Sure, you can sim ulat e inherit ance in t he relat ional m odel, but t hat is all it is, a sim ulat ion. The different sim ulat ion solut ions t o choose am ong are all com prom ises and carry overhead in st orage, speed, and/ or relat ional ugliness.
N ot e Deep and nat ive XML int egrat ion in t he dat abase seem s t o be t he newest way of t rying t o lessen t he problem of im pedance m ism at ch. But XML is act ually also a t hird m odel, a hierarchical one, which has im pedance m ism at ch wit h t he obj ect - orient ed world and t he relat ional world.
Because it has been t ypical for m y applicat ions t o use relat ional dat abases, t he im pedance m ism at ch has creat ed a big problem . The Dat a Mapper pat t ern [ Fowler PoEAA] can be used for dealing wit h t he problem . The Dat a Mapper pat t ern is about describing t he relat ionship bet ween t he Dom ain Model and t he Dat abase, and t hen t he shuffling work is t aken care of aut om at ically. Unfort unat ely t he Dat a Mapper pat t ern it self is a t ough one, especially in t he .NET plat form where Obj ect - Relat ional Mapper ( O/ R- m apper) product s are a couple of years behind. I n Java- land t here are several m at ure product s and even a st andardized spec called JDO [ Jordan/ Russell JDO] , which m akes t he t wo plat form s, as sim ilar as t hey are, t ot ally different from each ot her in t his respect . So it 's t im e t o leave t he area of Dom ain Models and dat abases and end t he archit ect ure sect ion by t alking about dist ribut ion.
Handle Distribution with Care Do you recognize t he " I have a new t ool, let 's really use it " syndrom e? I do. I 've been known t o suffer from it from t im e t o t im e. I t can be problem at ic, but I like t o t hink about it not j ust negat ively, but act ually a bit posit ively, t oo. Aft er all, it could be a sign of product ive curiosit y, healt hy progressive t hinking, and a never- ending appet it e for im provem ent s. OK, now I have set up som e excuses before t elling you som e horror st ories from m y past . About t en years ago, out cam e Microsoft Transact ion Server ( MTS) and suddenly it becam e pret t y easy for all COM developers t o build dist ribut ed syst em s. What happened? Loads of dist ribut ed syst em s have been built . I was t here wit h t he rest of t hem .
N ot e I 'm going t o use MTS as t he nam e here, but you could j ust as well use COM+ or COM+
Com ponent Services or Ent erprise Services.
Fine, for som e syst em s it was very suit able t o be dist ribut ed. Moving som e processing out from t he client and from t he dat abase t o an applicat ion server had it s benefit s, like resource sharing ( for exam ple, connect ion pooling at t he server- side so t hat hundreds of client s could be served by j ust a handful of dat abase connect ions) . These benefit s were very m uch needed for som e syst em s, and it becam e easier and m ore product ive t o get t hose benefit s.
Overuse Is Never Good Unfort unat ely, t he benefit s were so appealing t hat t hings went a bit overboard from t im e t o t im e. For exam ple, t he server- side work m ight be split at several different applicat ion servers. Not t hat t he applicat ion servers were cloned so t hat all client s could use any of t hem and get all services needed, but rat her t he client called one of t he applicat ion servers. That server called anot her applicat ion server, which called anot her applicat ion server. There were sm all benefit s, but huge drawbacks because of increased lat ency and lower availabilit y. Anot her m ist ake was t hat sim ple applicat ions wit h j ust t wo sim ult aneous users were writ t en for and execut ed in MTS. I t 's a very good exam ple of m aking a m ount ain out of a m olehill because of a belief t hat t he applicat ion will probably be very popular in som e dist ant fut ure and be used by t housands of users sim ult aneously. The problem wasn't j ust a m ore com plex operat ional environm ent , but t hat t he design of t he applicat ion had lot s of im plicat ions if you want ed t o be a good MTS cit izen. A t ypical exam ple of t his was t o avoid chat t y com m unicat ion bet ween cert ain ( or even worse, bet ween all) layers. Those im plicat ions did increase t he com plexit y of t he design t o quit e an ext ent . Well, overuse is never good. Did you not ice t hat I avoided t alking about t he norm al pit falls t hat beginners of dist ribut ed syst em s encount er when m aking t heir first at t em pt s wit h dist ribut ed syst em s, such at chat t y com m unicat ion? Well, even if, against all odds, for som e reason t hose m ist akes were avoided, t here are ot her, slight ly m ore subt le problem s. All t hese problem s led Mart in Fowler t o com e up wit h his First Law of Dist ribut ed Obj ect Design, which is " Don't dist ribut e" [ Fowler PoEAA] . I f you absolut ely don't have t o, don't do it . The cost and com plexit y will j ust go sky high. I t 's a law of nat ure.
Distribution Might Be Useful St ill, t here are good t hings about dist ribut ion t oo, bot h now and in t he fut ure, for exam ple: Fa u lt t ole r a n ce I f t here is j ust one single m achine t hat runs everyt hing, you have a problem if t hat m achine st art s burning. For som e applicat ions t hat risk is OK; for ot hers it 's unt hinkably bad. Se cu r it y There is a heat ed debat e over whet her or not it increases securit y t o split t he applicat ion across several m achines. Anyway, securit y concern is a com m on reason for why it is done. Sca la bilit y
For som e applicat ions, t he load is j ust t oo high for a single m achine t o cope wit h it . I t m ight also be t hat , financially, you don't want t o buy t he m ost expensive m achine around on day one. You m ay inst ead want t o add m ore m achines t o t he problem when t he problem increases, wit hout t hrowing away t he old m achines. I f you do need a dist ribut ed syst em , it 's ext rem ely im port ant t o t hink long and hard about t he design, because t hen you have one big design challenge t hat can easily creat e big t hroughput problem s, for exam ple. When t alking about dist ribut ion, m essaging is an im port ant concept .
Messaging Focus The focus of t his book is design of one applicat ion or one service, not so m uch about how t o orchest rat e several services. Even so, I t hink it 's good t o at least st art t hinking about m essaging ( if you haven't already) . I guess t his will j ust becom e m ore and m ore im port ant for us in t he fut ure.
Messages as a Core Programming Model Thingy I used t o t hink it was good t o abst ract away or hide t he net work in dist ribut ed syst em s. I n t his way, t he client program m er didn't know if a m et hod call was going t o t ranslat e t o a net work j um p or if it was j ust a local funct ion call. I liked t hat approach because I want ed t o sim plify t he life of t he client program m er so he or she could focus on st uff t hat 's im port ant t o creat e great user int erfaces and not get dist ract ed wit h t hings such as net work com m unicat ion. Let 's t ake a look at a sim ple exam ple. Assum e you have a Customer class, as shown in Figure 1- 5 .
Figu r e 1 - 5 . A Customer cla ss
I nst ances of t hat Customer class would t ypically ( and hopefully) live t heir life in t he sam e address space as t he consum er code, so t hat when t he consum er asks for t he nam e of t he Customer , it 's j ust a local call. There is not hing, however, t o physically st op you from let t ing Customer j ust be a Proxy [ GoF Design Pat t erns] , which in it s t urn will relay t he calls t o a Customer inst ance living at an applicat ion server. ( OK, it 's a bit sim plified, but t hat 's m ore or less what is going on behind t he scenes if you configure t he Customer class in MTS at an applicat ion server, for exam ple.) Szyperski point s out [ Szyperski Com ponent Soft ware] t hat locat ion t ransparency is bot h an advant age and a burden. The advant age is t hat all t ypes of com m unicat ion ( in process, int erprocess, and int erm achine) are m apped t o one abst ract ion, t he procedure call. The burden is t hat it hides t he significant cost difference bet ween t he different t ypes of calls. The difference is norm ally orders of m agnit ude in execut ion t im e. I t hink t he current t rend is t he opposit e of locat ion t ransparency and t o m ake t he cost ly m essages obvious by m aking m essages a core t hing in t he program m ing m odel. Trend- conscious as I am , I do
like t hat evolut ion. I like it j ust because it 's obvious t o t he client program m er t hat t here is going t o be a net work callit doesn't really m ean t hat it m ust be hard t o m ake. For exam ple, m essage fact ories could be provided, yet it st ill m akes it m uch clearer what will probably t ake t im e and what will not . You m ight wonder how will m essaging affect a dom ain m odel? The need for a dom ain m odel won't go away, but t here will probably be slight changes. The first exam ple t hat com es t o m ind is t hat insert s, as in a j ournal, are favored over updat es ( even for m odificat ions) . That m akes asynchronous com m unicat ion m uch m ore usable.
If Possible, Put It Off Until It's Done Better One very im port ant advant age of m essaging is t hat it increases t he execut ion flexibilit y so m uch. I n t he past , I t hink I 've been pret t y good at using bat ch j obs for long execut ions t hat didn't have t o execut e in real t im e. I t 's an old, and in m y opinion underused, m et hod for vast ly increasing response t im e for t he real t im e part of t he work, because t hen t he long execut ions would execut e in windows of low load, t ypically at night , for exam ple. Current ly I have writ t en t oo m any of m y applicat ions t o be synchronous. When a piece of funct ionalit y t akes t oo long t o execut e and doesn't have t o execut e in real t im e, I t hen have t o change t he funct ionalit y int o a bat ch process inst ead. What is execut ing in real t im e is changed from being t he com plet e piece of funct ionalit y int o t he request it self only. A m ore efficient solut ion t o t he problem would be t o t hink asynchronous m essages from t he st art as oft en as possible. Then t he funct ionalit y could run in real t im e if it 's appropriat e or be put on a m essage queue t o be execut ed as soon as possible or at given int ervals. ( The bat ch solut ion would be kind of built in from t he beginning.) A solut ion based on asynchronous m essages m ight require quit e a different m indset when you build your user int erface, but if you really challenge t he different design problem s, I t hink you will find t hat asynchronicit y is possible and a suit able way t o go m ore oft en t han you m ay have t hought in t he past . A few words t o end t his discussion: I n m y opinion it 's a good idea t o focus on t he core m odel it self, no m at t er what t he execut ion environm ent is. Then you can probably use it in several different sit uat ions, as t he need arises. Those were a few words about archit ect ure values t o value. Let 's m ove over t o t he process side.
Process Ingredients to Value I 'm not m uch of a process guy, but I st ill would like t o add a couple of t hought s. Before I get st art ed, what do I m ean by " process" ? I t 's t he m et hodology for m oving forward in proj ect s: What should be done, when, and how? The classic process was t he so- called wat erfall. Each phase followed aft er t he ot her in a st rict ly linear fashion wit h no going back what soever. A condensed descript ion could be t hat first a specificat ion was writ t en, and t hen t he syst em was built from t hat specificat ion, t hen t est ed, t hen deployed. Since t hen, num erous different processes have been int roduced over t he years, all wit h m erit s and pit falls of t heir own. A recent one, Ext rem e Program m ing ( XP) [ Beck XP] , gat hered as one process under t he um brella of Agile [ Cockburn Agile] processes, could probably be described as t he opposit e of wat erfall. One of t he basic ideas of XP is t hat it 's im possible t o know enough t o writ e a really good, det ailed specificat ion on day one. Knowledge evolves during t he proj ect , not j ust because t im e passes, but because part s of t he syst em are built , which is a very efficient way of gaining insight .
N ot e There's m uch m ore t o XP t han what I j ust said. See, for exam ple, [ Beck XP] for m ore inform at ion. I will discuss TDD and Refact oring, which have t heir root s in XP. Also not e t hat XP doesn't always st art from scrat ch. I n t he XP forum s t here is also a lot of int erest for XP and legacy syst em s. See, for exam ple, Obj ect - Orient ed Reengineering Pat t erns [ Dem eyer/ Ducasse/ Nierst rasz OORP] .
I t ry t o find a good com binat ion of sm aller ingredient s for t he sit uat ion. I have a couple of different current favorit e ingredient s. I 'd like t o discuss Dom ain- Driven Design, Test - Driven Developm ent , and Refact oring, but let 's st art wit h Up- Front Archit ect ure Design.
XP and a Focus on the User Anot her t hing t hat was considered new wit h XP was it s focus on user- cent ric developm ent . Users should be involved in t he proj ect s t hroughout t he com plet e developm ent . For a Swedish guy, t hat wasn't very revolut ionary. I don't know why, but we have a fairly long hist ory of user- cent ric developm ent in Sweden, long before XP. Wit hout m eaning t o sound as if Sweden is bet t er t han any ot her count ry, we have a long hist ory of also using t he wat erfall processes.
Up-Front Architecture Design Even t hough I like m any of t he Agile ideas about not m aking up front and prem at ure decisions about t hings t hat couldn't possibly be known at t hat st age, I don't t hink t hat we should st art t he const ruct ion of new proj ect s wit h only a blank sheet of paper, eit her. Most oft en, we have a fair am ount of inform at ion from t he very beginning ( t he m ore t he bet t er) about product ion environm ent , expect ed load, expect ed com plexit y, and so on. At least keep t hat in t he back of your m ind and use t he inform at ion for doing som e init ial/ early proof of concept of your up- front archit ect ure.
Reuse Ideas from Your Successful Architectures We can't afford t o st art from scrat ch wit h every new applicat ion, and t his is especially t rue when it com es t o t he archit ect ure. I usually t hink about m y current favorit e default archit ect ure and see how it fit s wit h t he sit uat ion and requirem ent s of t he new applicat ion t hat is t o be built . I also evaluat e t he last few applicat ions t o t hink of how t hey could have been im proved from an archit ect ure perspect ive, again wit h t he cont ext of t he new applicat ion in m ind. Always evaluat ing and t rying t o im prove is definit ely som et hing t o value. I f I assum e for t he m om ent t hat you like t he idea of t he Dom ain Model pat t ern [ Fowler PoEAA] , t he archit ect ure decisions could act ually be a bit less hard t o m ake init ially because a great deal of t he focus will go j ust t here, on t he Dom ain Model. Deciding whet her or not t o use t he Dom ain Model pat t ern is act ually an up- front archit ect ure design decision, and a very im port ant one because it will affect a lot of your upcom ing work. I t is also im port ant t o point out t hat even t hough you do m ake an up front decision, it 's not writ t en in st one, not even your decision about whet her or not t o ut ilize t he Dom ain Model pat t ern.
N ot e I recent ly heard t hat t he recom m endat ion from som e gurus was t o im plem ent t he Transact ion Script pat t ern [ Fowler PoEAA] wit h t he Recordset pat t ern [ Fowler PoEAA] for as long as possible and m ove t o anot her solut ion when it proved t o be a necessit y. I disagree. Sure, t here are worse t hings t hat could happen, but t hat t ransit ion is not som et hing I 'd like t o do lat e in t he proj ect because it will affect so m uch.
Consistency An im port ant reason for why it 's good t o m ake early archit ect ure decisions is so t hat your t eam of several developers will work in t he sam e direct ion. Som e guidelines are needed for t he sake of consist ency. The sam e is also t rue for I S depart m ent s t hat build m any applicat ions for t he com pany. I t 's very beneficial if t he archit ect ures of t he applicat ions are som ewhat sim ilar as it m akes it m uch easier and m ore efficient t o m ove people bet ween proj ect s.
Software Factories
This brings us nicely t o t alking a lit t le bit about Soft ware Fact ories [ Greenfield/ Short SF] ( wit h inspirat ion from Product Line Archit ect ure [ Bosch Product Line] ) . The idea of Soft ware Fact ories is t o have t wo lines in t he soft ware com pany. One line creat es archit ect ures, fram eworks, and such t o be used for fam ilies of applicat ions. The ot her line creat es t he applicat ions by using what t he first line has produced, and t hereby am ort izing t he cost of t he fram eworks on several proj ect s.
N ot e A problem is t hat it 's t roublesom e t o j ust invent a fram ework. I t 's probably a bet t er idea t o harvest inst ead [ Fowler Harvest edFram ework] .
Anot her t hing t hat is som ewhat problem at ic wit h Soft ware Fact ories, t hough, is t hat t hey probably require pret t y large organizat ions before being efficient t o use. A friend t hat has used Product Line Archit ect ures said t hat t he organizat ion needs t o have a head count of t housands rat her t han fift y or a hundred because of t he large invest m ent s and overhead cost s. He also said ( and I 've heard t he sam e from ot hers) t hat even in organizat ions t hat have st art ed t o use Product Line Archit ect ures, it 's not necessarily and aut om at ically used all t he t im e. The overhead and bureaucracy it brings wit h it should not be underest im at ed. Think about a t iny fram ework t hat you use in several applicat ions and t hen t hink m uch bigger and you get a feeling for it . That said, I definit ely t hink t he Soft ware Fact ories init iat ive is int erest ing. At t he heart of Soft ware Fact ories is t hat of Dom ain Specific Languages ( DSL) [ Fowler LW] . A popdescript ion could be t hat DSL is about dealing wit h sub- problem s wit h languages specialized for t he t ask at hand. The languages t hem selves can be graphical or t ext ual. They can also be generic, as wit h XML, UML and C# , or specific, like t he WinForm s edit or in VS.NET or a lit t le language you define on your own for a cert ain t ask. Anot her approach, but wit h m any sim ilarit ies t o DSL, is Model- Driven Archit ect ure.
Model-Driven Architecture Model- Driven Archit ect ure ( MDA) [ OMG MDA] is som et hing like " program m ing by drawing diagram s in UML." One com m on idea from t he MDA arena is t o creat e a Plat form I ndependent Model ( PI M) t hat can t hen be t ransform ed int o a Plat form Specific Model ( PSM) , and from t here t ransform ed int o execut able form . Thinking about it , it feels like writ ing 100% of a new program wit h a 3GL, such as C# , is overkill. I t should be t im e t o increase t he abst ract ion level. I t hink one problem m any are seeing wit h MDA is it s t ight coupling t o UML. One part of t he problem is t he loss of precision when going bet ween code and UML; anot her part of t he problem is t hat UML is a generic language wit h t he pros and cons t hat com es wit h t hat . For t he m om ent , let 's sum m arize t hose approaches ( bot h DSL and MDA) wit h t he t erm Model- Driven Developm ent . I feel confident t o say t hat we are m oving in t hat direct ion, so Model- Driven Developm ent will probably be a big t hing. Even t oday, you can go a pret t y long way wit h t he current t ools for Model- Driven Developm ent . Furt her on, I also t hink bot h approaches of DSL and MDA fit very well wit h Dom ain- Driven Design, especially t he m indset of focusing on t he m odel.
Domain-Driven Design We have already discussed m odel focus and Dom ain- Driven Design ( DDD) in t he archit ect ure sect ion, but I 'd like t o add a few words about DDD in t he process perspect ive also. Using DDD [ Evans DDD] as t he process will focus m ost of t he energy on building a good m odel and im plem ent ing it as closely as possible in soft ware. What it 's all about is creat ing as sim ple a m odel as possible, one t hat st ill capt ures what 's im port ant for t he dom ain of t he applicat ion. During developm ent , t he process could really be described as knowledge- crunching by t he developers and dom ain expert s t oget her. The knowledge t hat is gained is put int o t he m odel.
Find Old Knowledge as a Shortcut Of course, not all knowledge has t o be gained from scrat ch. Depending upon t he dom ain, t here could well be loads of knowledge available in books, and not j ust in specific books for t he dom ain in quest ion. There is act ually inform at ion in a couple of soft ware books, t oo. Unt il recent ly, I would only consider t he books called Dat a Model Pat t erns [ Hay Dat a Model Pat t erns] and Analysis Pat t erns [ Fowler Analysis Pat t erns] , but I would st rongly suggest t hat you get your hands on a book about Archet ype Pat t erns as well. The book is called Ent erprise Pat t erns and MDA [ Arlow/ Neust adt Archet ype Pat t erns] .
Refactor for Deeper Knowledge A value I definit ely t hink should be valued is cont inuous evaluat ion, and t hat goes for DDD, as well. I s t he current m odel t he best one? Every now and t hen, quest ion t he current m odel const ruct ively, and if you com e up wit h im port ant sim plificat ions, don't be afraid t o refact or. The sam e also applies when you find som et hing new. A couple of sim ple refact orings m ight m ake t hat new feat ure not only possible but also easy t o achieve so it fit s well wit hin t he m odel. Refact orings alone m ight also lead t o deeper knowledge, like when you do a refact oring t hat can open everyt hing up and lead t o one of t hose rare " eureka! " m om ent s. We will com e back t o refact oring short ly, but first I 'd like t o t alk about som et hing closely relat ed, nam ely Test - Driven Developm ent .
Test-Driven Development I 've oft en heard developers say t hat t hey can't use aut om at ic unit t est ing because t hey don't have any good t ools. Well, t he m indset is m uch m ore im port ant t han t he t ools, alt hough t ools do help, of course. I wrot e m y own t ool ( see Figure 1- 6 ) a couple of years ago and used it for regist ering and execut ing t est s of st ored procedures, COM com ponent s, and .NET classes. Thanks t o t his t ool, I could skip t hose form s wit h 97 but t ons t hat have t o be pressed in a cert ain order in order t o execut e t he t est s.
Figu r e 1 - 6 . Scr e e n sh ot of m y old t e st t ool ca lle d Jn sk Te st [View full size image]
Lat er on, when NUnit [ NUnit ] ( a derivat e from t he ot her xUnit versions) was released ( see Figure 17) , I st art ed using t hat t ool inst ead. Using NUnit is way m ore product ive. For exam ple, m y t ool didn't reflect on what t he exist ing t est s were. I nst ead you had t o explicit ly regist er inform at ion about t hem .
Figu r e 1 - 7 . N Un it , t h e GUI ve r sion [View full size image]
N ot e Now I use anot her t ool, called Test driven.Net , but as I said, what t ool you use is of less im port ance.
No m at t er what process you use, you can use aut om at ic unit t est s. For an even larger posit ive effect , I st rongly recom m end you find out if Test - Driven Developm ent ( TDD) is for you.
The Next Level TDD is about writ ing t est s before writ ing t he real code. I n doing t his, t he t est s will drive your design and program m ing. TDD sounds dull and boring, and developers oft en expect it t o be a pain in t he backside. They couldn't be m ore wrong! I n m y experience, t he opposit e is t rueit 's act ually great fun, which cam e as a surprise t o m e. I guess t he reason t hat it is such fun is t hat you get inst ant feedback on your changes, and because we are professionals, we enj oy creat ing high- qualit y applicat ions. Anot her way t o put it is t hat TDD isn't about t est ing. I t 's about program m ing and design. I t 's about writ ing sim pler, clearer, and m ore robust code! ( Sure, t he " side- effect " of creat ed unit t est s is ext rem ely im port ant ! )
Why TDD? The reason I st art ed wit h TDD in t he first place was t hat I want ed t o im prove t he qualit y of m y proj ect s. I m provem ent in qualit y is probably t he m ost obvious and im port ant effect . We don't want t o creat e applicat ions t hat crash when t he cust om er uses t hem for t he first t im e or applicat ions t hat break down when we need t o enhance t hem . I t 's j ust not accept able anym ore. TDD won't aut om at ically help you never release product s wit h bugs again, but t he qualit y will im prove.
N ot e The aut om at ic t est s t hem selves aren't t he prim ary reason for TDD; t hey are nice side effect s. I f qualit y is everyt hing, t here are ot her form al m et hods, but for m any scenarios t hey are considered t oo " expensive." Again, cont ext is im port ant .
You can see t he effect of im proved qualit y by writ ing t est s aft er t he real code. What I m ean is t hat you don't have t o apply TDD ( which m eans writ ing t est s before t he real code) , you j ust need a lot of discipline. On t he ot her hand, using TDD get s t he t est s writ t en. Ot herwise, t here is a very great risk t hat you won't writ e any t est s when you're pressed for t im e, which always happens when it get s t o a lat e st age in t he proj ect s. Again, TDD m akes t he t est s happen. The second effect you can expect when applying TDD is t o see im proved sim plicit y of design. I n t he words of t wo popular sayings, " Sim ple is beaut iful" and " KI SS." They are very im port ant because, for exam ple, com plexit y produces bugs. I nst ead of creat ing loads of advanced blueprint s covering every lit t le det ail upfront , when using TDD you will focus on t he core cust om er requirem ent s and j ust add t he st uff t he cust om er needs. You get m ore of a cust om er perspect ive t han a t echnical perspect ive. TDD is not about skipping design. On t he cont rary, you are doing design t he whole t im e when using TDD. I n t he past I 've been very good at overcom plicat ing sim ple t hings. TDD helps m e keep focused and not do anyt hing ot her t han what is really necessary now . This effect ( get t ing im proved sim plicit y of
design) requires TDD. I t 's not enough t o j ust writ e t he t est s aft erwards. Yet anot her effect of TDD is t hat you will get high product ivit y all t he way. This m ight sound count erint uit ive at first . When you st art a new proj ect , it feels very product ive t o get going and writ e t he real code. At first you are very product ive, but it 's very com m on t hat t he product ivit y com plet ely drops near t he end of t he proj ect . Bugs st art cropping up; t he cust om er decides on a couple of pret t y subst ant ial changes t hat upset everyt hing; you find out t hat you have m isunderst ood som e t hings...well, you get t he pict ure. Test s will force you t o challenge t he requirem ent s and t o challenge t hem early. Thereby, you will find out early if you have underst ood. You will also reveal lacking and cont radict ory requirem ent sagain, early. By t he way, you shouldn't ask t he cust om er if you should use TDD or not , at least not if you're asking for m ore paym ent / t im e/ what ever at t he sam e t im e. He will j ust t ell you t o do it right inst ead. When considering t he proj ect from st art t o finish, if using TDD incurs no ext ra cost , I believe you should j ust go ahead. The cust om er will be happy aft erward when he get s t he qualit y he expect s.
N ot e Let 's for a m om ent skip TDD and focus only on aut om at ic t est s. A colleague of m ine has been ext rem ely skept ical of t he need for aut om at ic unit t est s ( creat ed before or aft er real code) . He t old m e t hat during his t wo- decade career, aut om at ic unit t est s would not have helped him once. However, I t hink he changed his m ind a bit recent ly. We were working t oget her on a proj ect where he wrot e a COM com ponent in C+ + and I wrot e t est s as specificat ions and as j ust t est s. When we were done, t he cust om er changed one t hing in t he requirem ent s. My colleague m ade a sm all change, but t he t est s caught a bug t hat occurred j ust four t im es in 1,000 execut ions. The bug was found aft er only seconds of t est ing, com pared t o hours if it had been done m anually. And if it had been done m anually, t he bug would m ost probably not have been found at all, but would have shown it self during product ion.
The TDD Flow Now I have t ried t o get you m ot ivat ed t o st art wit h TDD, so let 's have a closer look at how t he process flows. ( We'll get back t o t his in Chapt er 3 when we will invest igat e it in a bit m ore dept h wit h a real world dem o.) Assum ing you have a decent idea about t he requirem ent s, t he flow goes like t his: First of all, you st art writ ing a t est . You m ake t he t est fail m eaningfully so t hat you can be sure t hat t he t est is t est ing what you t hink. This is a sim ple and im port ant rule, but even so, I have skipped over it several t im es and t hat is j ust asking for t rouble. The second st ep is t o writ e t he sim plest possible code t hat m akes t he t est pass. The t hird st ep is t o refact or if necessary, because you ident ify code t hat sm ells ( for exam ple, code duplicat ion) , and t hen you st art all over again, adding anot her t est . I f we use NUnit lingo, t he first st ep should give you a red light / bar, and t he second st ep should give you a green light / bar.
I m ent ioned refact oring previously and as t he t hird st ep in t he general process of TDD, and I t hink I should briefly explain t he t erm a bit m ore.
Refactoring Cont inuous learning was som et hing we heard about in school all t he t im e. I t 's very m uch t rue in t he case of refact oring [ Fowler R] refact oring t o get a bet t er m odel, for exam ple. Refact oring is about m aking sm all, well- known changes st ep by st ep in order t o im prove t he design of exist ing code. That is, t o im prove it s m aint ainabilit y wit hout changing it s observed behavior. Anot her way t o say it is t o change how , not what . I n a nut shell, what refact oring does is t o t ake you from sm elly code t o nice code. I t 's as sim ple as t hat . So you don't have t o com e up wit h a perfect design up- front . That is good news, because it can't be done anyway.
Why Use Refactoring? None of us have any t rouble in recognizing sm elly code. What m ight be m ore t roublesom e is t o know when t o fix it . As I see it , you should deal wit h t he problem as soon as it arises. You should use refact oring because wit hout cont inuous m aint enance of your code, it will st art t o degenerat e and cr um ble.
N ot e Mark Burhop said t he following: " A good friend keeps a list of Soft ware Developm ent Laws. One goes som et hing like " Code, left unt ouched, will develop bugs."
Let 's use t he analogy of your hom e. Problem s you choose t o ignore, such as fixing windows, repairing t he roof, paint ing t he woodwork, and so on, ignored problem s like t hese will grow in t im e. That 's an im m ut able law. So sooner or lat er your house will fall t o bit s, and at t hat point it 's wort hless. Nobody want s t hat sit uat ion, right ? Soft ware is different because it 's not built of organic m at erial and won't becom e affect ed from weat her and wind if not changed. St ill, we int uit ively have a feeling for what 's happening over t im e wit h soft ware when refact oring isn't applied during bug fixes and when t he soft ware is ext ended.
How Should I Use Refactoring? Refact oring can be used in all phases of t he applicat ion lifecycle; for inst ance, during developm ent of t he first version of an applicat ion. But j ust assum e we don't use refact oring, but an up- front , t radit ional design heavy process inst ead ( now oft en referred t o as Big Design Up- Front , BDUF) . We will spend quit e a lot of t im e on init ial det ailed design, creat ing loads of det ailed UML diagram s, but as a result we will expect t he developm ent t o go very sm oot hly and quickly. Even assum ing t hat it does, t here is st ill a risk t hat t he code will be, well, sm elly.
I nst ead, let 's say we j ust accept t he fact t hat we can't get it right up front t he first t im e. I n t his case, a slight ly different approach is t o m ove som e of t he effort from init ial det ailed design over t o developm ent inst ead ( and of course all developm ent , especially in t his case, is design) and t o be prepared for doing refact oring cont inuously when we learn m ore. Learning m ore is exact ly what we do during developm ent , and as I see it , t his approach result s in higher qualit y code. So inst ead of doing t oo m uch guessing, we do m ore learning and proofing!
N ot e I was probably overly posit ive t o BDUF so as t o not dist ract you from t he point t hat I was aft er regarding sm elly code. Doing a lot of guesswork on day one will oft en lead t o wast ed t im e because it is j ust guesswork. My friend Jim m y Ekbäck com m ent ed on t his by saying, " BDUF can be even worse t han wast ed t im e because of incorrect guesses. BDUF can also lead t o self- fulfilled prophesies."
Refactoring + TDD = True I n order t o be able t o use refact oring in a safe way, you m ust carry out ext ensive t est s. I f you don't , you will int roduce bugs and/ or you will priorit ize, not m aking any changes sim ply for t he sake of m aint ainabilit y, because t he risk of int roducing bugs is j ust t oo large. And when you st op m aking changes because of m aint ainabilit y, your code has slowly st art ed t o degrade.
N ot e You will find m uch m ore coverage, wit h focus on hands- on exam ples, about bot h TDD and Refact oring in Chapt er 3.
I t 's a good idea t o use TDD and refact oring for bugfixing also. First expose t he bug wit h a red t est , t hen solve t he bug so you get green, and t hen refact or.
Which Ingredient or a Combination? Again, I 'm sure m any of you are wondering which way t o go. For inst ance, should you focus on upfront design or TDD? As I see it , you can m ix up- front design and TDD successfully. For exam ple, set up som e up- front archit ect ure, work wit h Dom ain- Driven Design, and for each piece of behavior build it wit h TDD ( including refact oring) . Then go back t o your archit ect ure and change it in accordance wit h what you have learned. Then work wit h Dom ain- Driven Design, and cont inue like t hat .
N ot e I have t o adm it t hat I oft en fall back int o t he old habit of doing det ailed up- front design. However, t hinking about t he problem in different ways is oft en t he m ost efficient t hing t o do. A lit t le bit t op- down, a lit t le bit bot t om - up. A lit t le bit inside out , a lit t le bit out side in.
I t hink it 's pret t y well known t hat a Big Design Up- Front ( BDUF) has som e big problem s. At t he sam e t im e, m ost oft en we know som e t hings from day one. I t 's a m at t er of balance. Finally, a last rem ark regarding DDD and TDD: Dom ain Models are very suit able for TDD. Sure, you can also apply TDD wit h m ore dat abase- orient ed design, but I haven't been able t o apply it as gracefully and product ively as when I 'm working wit h Dom ain Models.
N ot e When I discussed TDD and/ or DDD wit h Eric Evans he said t he following, which I t hink is spot on: " Myself, I act ually play wit h t he m odel while writ ing t he t est s. Writ ing t he t est let s m e see what sort of client code different assignm ent s of responsibilit y would produce, as well as t he fine- t uning of m et hod nam es and so on t o com m unicat e int ent ion and have a good flow."
No m at t er if you focus on TDD or BDUF, t here are lot s of t echniques t hat are useful anyway. The chapt er will end wit h focusing on a couple of such t hings, such as operat ional aspect s, but t he first exam ple is called Cont inuous I nt egrat ion.
Continuous Integration I 'm sure you all recognize how big a showst opper m anual int egrat ion on a m ont hly basis, for exam ple, can beeven if your t eam j ust consist s of t wo developers on a proj ect . The problem increases exponent ially when you add m ore developers. Am ong ot her t hings, it cost s lot s of t im e, it is error prone, and you will find t hat it won't happen as planned. " Just one m ore day" will be a com m on sent ence used in t he proj ect . My friend Claudio Perrone is j ust t he right guy t o describe t he popular solut ion t o t his, known as cont inuous int egrat ion.
The Integration Problem By Claudio Perrone Sooner or lat er, all t he com ponent s t hat have been creat ed and m odified by different developers need t o be built and assem bled t oget her t o form a single syst em . I n t he past , I 've wit nessed ( and, yes, occasionally caused) spect acular delays in proj ect s originat ed by last - m inut e int egrat ion effort s where unexpect ed defect s were discovered in t he final phases of a developm ent cycle. A build m ay fail for a variet y of reasons, oft en in com binat ion, such as Poorly t est ed com ponent s Wrong ( but oft en plausible) assum pt ions about except ion handling, null values, param et ers, global variables, and so on Weak design Unpredict ed behavior on different plat form s Unexpect ed differences bet ween release and debug builds Obfuscat ion issues Set up and perm ission issues Bugs in t he underlying fram eworks Not e t hat , despit e all t he best effort s, only som e of t hese problem s can be lim it ed by t he discipline and com m unicat ion capabilit ies of t he developers involved. The realit y is t hat if t he t eam does not int egrat e oft en and if t he num ber of new classes in t he syst em is sufficient ly large, you m ay find yourself anxiously hunt ing com bined bugs foreveran unpleasant scenario com m only known as " I nt egrat ion Hell."
The Solution (Or at Least a Big Step in the Right Direction)
The key t o significant ly reducing int egrat ion problem s is t o generat e your builds aut om at ically and t o use an increm ent al int egrat ion st rat egy where all t he code is rebuilt and t est ed at least daily, if not cont inuously. The idea is t hat if your recent ly added code breaks a build, you eit her fix it im m ediat ely or roll back t he changes t o rest ore t he syst em t o t he last known good st at e. The fundam ent al part s t hat const it ut e a cont inuous int egrat ion syst em are as follows: A m achine dedicat ed t o t he build process A source cont rol syst em t hat act s as a cent ral reposit ory for all source code A m onit oring service t hat checks t he source cont rol syst em for changes t o t he source code A script ing engine t hat , when t riggered by t he previous service, is able t o creat e builds A report ing syst em t hat can give im m ediat e feedback about t he result s of a build There are several int egrat ion product s available t hat you m ay want t o invest igat e. Current ly, m y favorit e is CruiseCont rol.NET [ CC.NET] , which I use in com binat ion wit h NAnt , a very popular XMLbased build engine. Bot h t ools are open source. I t t akes quit e an effort t o configure CruiseCont rol.NET but , once up and running, it t akes care of calling NAnt script s whenever t here is a change in t he codebase. I t not ifies you of t he progress and st at us of all current builds using a wide variet y of client m odules, including a lit t le applicat ion t hat uses a Windows t ray icon t o show sum m ary inform at ion at a glance t hrough color coding and not ificat ion balloons.
Lessons Learned in My Organization When we first cont em plat ed t he possibilit y of im plem ent ing a cont inuous int egrat ion syst em at I nnerWorkings, m ost of us t hought t hat it was a really great idea. Alt hough we were already under severe scheduling pressure, we knew t hat it was definit ely wort h invest ing a few days t o im plem ent t he syst em . I f I t hink about it now, however, we cert ainly underest im at ed t he profound im pact t hat such a syst em would have on t he qualit y of our product s and t he confidence of our t eam . Today we have about 500 cont inuously int egrat ed solut ions, and t he num ber is st ill increasing. A t hird of t hese solut ions share a cust om - built com m on fram ework, which is also int egrat ed. I nt egrat ion st eps for all t hese solut ions include com pilat ion, obfuscat ion, packaging, t est ing, and deploym ent using different configurat ions and plat form s. Before we st art ed t his proj ect , we were t old t hat it would be im possible t o int egrat e all of t hese solut ions cont inuously. However, I 'm convinced t hat it would have been im possible t o do ot herwise and t hat t his effort const it ut es a crit ical fact or in our success.
Further Information A good st art ing point for learning m ore about cont inuous int egrat ion is a paper writ t en by Mart in Fowler and Mat t hew Foem m el called " Cont inuous I nt egrat ion" [ Fowler/ Foem m el CI ] . Thanks, Claudio! Now let 's m ove on t o som e operat ional aspect s.
Don't Forget About Operations Not t oo long ago, I was t alking t o a t eam at a large Swedish com pany. I t alked, for exam ple, about t he Valhalla fram ework and how it looked at t hat part icular point in t im e. They asked m e how we had dealt wit h operat ional m echanism s, such as logging, configurat ion, securit y, and so on. When I t old t hem t hat we hadn't added t hat yet , t hey first went quiet and t hen t hey st art ed laughing out loud. They said t hey had spent years in t heir own fram ework wit h t hose aspect s, and we hadn't even st art ed t hinking about it . Luckily, I could defend m yself t o som e ext ent . We had been t hinking quit e a lot about it , but we want ed t o set t he core part s of t he fram ework before adding t he operat ional m echanism s. Aft er all, t he core part s influence how t he m echanism should look. I could also direct t hem t o m y last book [ Nilsson NED] where I t alked a lot in t he init ial chapt ers about m echanism s like t hose ( such as t racing, logging, and configurat ion) .
An Example of When a Mechanism Is Needed Why are t he operat ional aspect s im port ant ? Let 's t ake an exam ple. Assum e an applicat ion t hat is in product ion lacks t racing. ( This isn't j ust fict ional. I know t hat t his operat ional aspect is forgot t en pret t y oft en. Even t hough for t he last few years I have been t alking m yself blue in t he face about t his, I have old applicat ions in product ion wit hout t racing built - in m yself.) When a weird problem occurs t hat isn't revealing t oo m uch about it self in t he error log inform at ion, t he reason for t he problem is very hard t o find and t he problem is very hard t o solve.
No Tracing in Place You could always add t racing at t hat part icular point in t im e, but it would probably t ake you a couple of days at least . I f t he problem is serious, t he cust om er will expect you t o find and solve t he problem in less t im e t han a couple of days. A com m onand m ost oft en pret t y inefficient way t o approach t his is t o m ake ad- hoc changes and aft er each change cross your fingers and hope t hat t he problem is gone. What you probably do inst ead is add ad- hoc t racing here and t here. I t will m ake your code m uch uglier, and it will t ake som e t im e before you t rack down t he problem . The next t im e t here is anot her problem , very lit t le has changed. You will be back at square one. What m ight also be possible is t o run a debugger in t he product ion environm ent . However, t here are problem s wit h t his such as you m ight int erfere t oo m uch wit h ot her syst em s or you m ight have obfuscat ed t he code wit h som e t ool so t hat it 's hard t o debug. I t 's also risky t o change t he code in product ion, even if t he change is as sm all as adding t racing. Not a big risk, but it 's t here.
N ot e I f you have t he possibilit y of using Aspect - Orient ed Program m ing ( AOP) , it m ight not t ake m ore t han a few m inut es t o add t racing aft erward. We will discuss AOP quit e a lot in Chapt er 10, " Design Techniques t o Em brace."
Tracing in Place I f you have a working t racing solut ion in place, you know how efficient it m ight be t o find and solve t he problem inst ead. The days- long delay is gone, and you are on t he way t o t racking down t he problem in m inut es. So it 's im port ant t o be careful and not t hink " You Aren't Going t o Need I t " ( YAGNI ) t oo oft en when it com es t o operat ional m echanism s. Using YAGNI oft en will cost t oo m uch when it com es t o adding t he m echanism if ( or rat her when) you will need it . Rem em ber, t he idea wit h YAGNI is t hat t he cost of adding som et hing is pret t y m uch t he sam e now and lat er, in which case you can always wait unt il you really need it . When t he cost is low now and high lat er, and t here's a good chance you will need it , you should m ake a different decision.
Some Examples of Operational Mechanisms Here I have list ed a short num ber of operat ional m echanism s t hat can be considered for m ost ent erprise scale applicat ions: Tr a cin g As we j ust discussed, it 's nice t o be able t o list en t o what is going on at t he sam e t im e as users run scenarios in t he syst em . This is not only a very efficient solut ion for t racking down bugs, but it can be used for invest igat ing where t he bot t lenecks are locat ed, for exam ple. Loggin g Errors, warnings, and inform at ion m essages m ust be logged. This is ext rem ely im port ant for invest igat ing problem s aft er t hey have occurred. We can't expect t he users t o writ e down t he exact m essages for us. I t m ight also be t hat we want t o collect inform at ion t hat we don't want t o show t o t he users. Con fig Have you had t o recom pile old applicat ions j ust because t he dat abase server was swit ched t o a new m achine wit h a new nam e? I have. Of course, t hat kind of inform at ion should be configurable and depending on t he applicat ion, t his m ight be t he case for loads of inform at ion. Pe r for m a n ce m on it or in g Get t ing perform ance m onit oring based on Dom ain Model inform at ion and ot her part s of your applicat ion is ext rem ely helpful for t racking down problem s, but also for keeping a proact ive eye on t he syst em . By doing t hat , you can easily t rack t hat it now t akes perhaps 30% longer t o execut e a cert ain scenario com pared t o a t im e t wo m ont hs ago. Se cu r it y
These days, t his one probably doesn't need any furt her explanat ion. We obviously need t o carefully t hink t hrough t hings like aut hent icat ion and aut horizat ion. We also m ust prot ect our applicat ions against at t acks of different kinds. Au dit in g As one part of t he securit y aspect s, it 's im port ant t o have audit ing so t hat it 's possible t o check aft erwards who did what when.
It's Not Just Our Fault I n t he defense of developers, I know I have asked operat ional people several t im es about t heir requirem ent s regarding operat ional m echanism s, and t hey haven't said very m uch. I guess t hey haven't been spoiled wit h a lot of support from t he applicat ions. That said, an appealing way of dealing wit h t his is t o, if you can, get som e resources from t he operat ions side early on t o act explicit ly as a st akeholder on t he syst em , so t hat you creat e t he operat ional m echanism s t hat are really needed. The ordinary cust om er of t he syst em isn't a good requirem ent creat or here. The operat ional m echanism s are t ypical exam ples of non- funct ional requirem ent s, and t he ordinary cust om ers won't norm ally add m uch t here. The flexibilit y for your m echanism s m ight be im port ant because different cust om ers use different operat ional plat form s. There are st andards such as Windows Managem ent I nst rum ent at ion ( WMI ) , but it 's wise t o build in flexibilit y if you build a fram ework for t his so you can easily swit ch t o different out put form at s for t he logging, for exam ple. One cust om er uses CA Unicent er, anot her uses Microsoft Operat ions Manager ( MOM) , yet anot her m ight use som e product t hat won't underst and WMI , and so on.
Summary We ended t he chapt er wit h a few words about operat ional aspect s. We won't discuss t hat m uch m ore in t his book. I nst ead, t he focus will be about t he core of t he applicat ions, t he core business value. The discussion in t his chapt er about what is im port ant for m odern soft ware developm ent is cert ainly not exhaust ive. My hope was t o briefly discuss a couple of values wort h considering for every developer. On t he way we int roduced Dom ain- Driven Design, Dom ain Models, Test - Driven Developm ent , Pat t erns, and a lot of ot her concept s, bot h regarding archit ect ure and regarding processes. The t hree values I would like t o st ress again are balance, cont ext - awareness, and cont inuous learning. Those are valuable values for developers and archit ect s, and everybody else, t oo. So wit h t he scene set up, it 's now t im e t o discuss pat t erns of different t ypes som e m ore.
Chapter 2. A Head Start on Patterns We are const ant ly facing new design problem s. We always solve t he problem s, but som et im es we find t hat we have backed ourselves int o a corner. Som et im es we find t hat t he solut ion has serious drawbacks, and som et im es we creat e bad solut ions. By reusing good, well- proven, and flexible solut ions we m inim ize t hese risks and we reach t he desired result fast er. A t ool t o use for t his is pat t erns. Thanks t o t he higher abst ract ion level t hat com es from pat t erns, we can also st art and succeed wit h even larger design problem s. Pat t ern awareness also leads t o bet t er underst anding bet ween developerssyst em developm ent is very m uch about com m unicat ion. I 've heard it m any t im es. Pat t erns are academ ic nonsense, useless and elit ist . I f t his is also how you feel, m y aim in t his chapt er is t o show you t he opposit e, because not hing could be m ore wrong. Pat t erns can be very pragm at ic, highly useful in day- t o- day work, and ext rem ely int erest ing t o all ( or at least m ost ) developers. Maybe you haven't not iced, but I have already discussed several pat t erns. One exam ple is t he Dom ain Model pat t ern [ Fowler PoEAA] t hat I brought up in Chapt er 1, " Values t o Value." I n t his chapt er, we will discuss t hree different cat egories of pat t erns: nam ely Design Pat t erns ( generic and applicat ion- t ype specific) , Archit ect ural Pat t erns, and Dom ain Pat t erns.
N ot e Please not e t hat t he cat egorizat ions here are a bit fuzzy, and not at all as im port ant or int erest ing as t he pat t erns t hem selves. So if t he cat egorizat ions don't provide any help t o you, don't let t hem get in t he way for you.
Even if you are already pat t ern- conscious, I t hink you'll find t his chapt er int erest ing. I won't reuse old explanat ions, but will provide m y own view of t he pat t erns. For exam ple, t he discussion I 'm going t o use will be very Dom ain Model- focused, which is not t ypically t he case when it com es t o Design Pat t erns, for exam ple. I f not hing else, I hope you'll find t he reflect ions here and t here of int erest . Before get t ing st art ed, t hough, I 'd like t o t ake you t hrough a generic discussion of t he concept of pat t erns and why you should learn about t hem .
A Little Bit About Patterns Pat t erns provide sim ple, elegant solut ions t o recurring design problem s. The key advant ages pat t erns provide are flexibilit y, m odularit y, and creat ing underst andable and clear design. Not e t hat I skipped reusabilit y, alt hough it 's a bit unfair. Pat t erns t ake away focus from code reuse and m ove t he focus t o knowledge reuse inst ead. So pat t erns are very m uch about reusabilit y, t oo, but j ust not in t he way we usually t hink about reuse.
N ot e Gregory Young point ed out t hat m any pat t erns are about reuse, t hrough decoupling. The Dependency I nj ect ion pat t ern ( which will be discussed in Chapt er 10, " Design Techniques t o Em brace" ) is a good exam ple of t hat .
When you st udy pat t erns, you m ight t hink " OK, isn't t hat how we always do it ?" An im port ant point about pat t erns is t hat t hey aren't inv ent ed, but rat her harvest ed or dist illed. I t 's about proven solut ions. But t he solut ion part isn't t he only piece of a pat t ern. They are described in t hree pieces: t he cont ext , t he problem , and t he solut ion. Learning from your m ist akes is very powerful, but from t im e t o t im e it 's nice t o t ake a short cut by st udying ot her people's am assed knowledge, which is a good reason for learning pat t erns. Let 's see if we can find ot her reasons.
Why Learn About Patterns? The m ost obvious reason is probably t hat pat t erns are good abst ract ions t hat provide building blocks for syst em design. I f a developm ent t eam is pat t erns- conscious, pat t erns becom e a very im port ant part of t he language. I nst ead of having t o describe each and every design idea in m inut e det ail, it 's oft en enough t o say a pat t ern nam e and everybody in t he t eam can evaluat e whet her or not it 's a good idea for t hat part icular problem . Adding pat t erns t o t he t eam 's language m ight be t he single m ost im port ant reason for em bracing pat t erns because t he com m on underst anding, richness, and expressiveness of t he language increase. Again, developm ent is very m uch about com m unicat ion. Anot her reason I like pat t erns is t hat being able t o ut ilize pat t erns is a long- last ing skill. As a com parison, I learned SQL in around 1988, and I can st ill m ake a good living from j ust working wit h t hat . The product s and plat form s I work wit h have changed several t im es, t hough t he underlying concept s are t he sam e. Pat t erns are sim ilar. The Design Pat t erns book [ GoF Design Pat t erns] cam e out in 1995, and it 's st ill ext rem ely relevant t oday. Also wort h point ing out is t hat pat t erns are language- / product - / plat form - agnost ic. ( Different plat form s m ight have specific support for cert ain im plem ent at ion variat ions, and it 's also t he case t hat t he [ GoF Design Pat t erns] have been writ t en wit h obj ect orient at ion as an assum pt ion.) I f you st udy Design Pat t erns [ GoF Design Pat t erns] , you will find t hat t he pat t erns t here are very
m uch in line wit h t he principles of good obj ect - orient ed design. What is good obj ect - orient ed design, you m ight ask? Robert C. Mart in discusses som e such principles in Agile Soft ware Developm ent : Principles, Pat t erns, and Pract ices [ Mart in PPP] . Exam ples include t he Single Responsibilit y Principle ( SRP) , t he Open- Closed Principle ( OCP) and t he Liskov Subst it ut ion Principle ( LSP) .
More on Martin's Principles... A bit of explanat ion of Mart in's principles follows: Single Responsibilit y Principle ( SRP) An it em such as a class should j ust have one responsibilit y and solve t hat responsibilit y well. I f a class is responsible bot h for present at ion and dat a access, t hat 's a good exam ple of a class breaking SRP. Open- Closed Principle ( OCP) A class should be closed for m odificat ion, but open for ext ension. When you change a class, t here is always a risk t hat you will break som et hing. But if inst ead of m odifying t he class you ext end it wit h a sub- class, t hat 's a less risky change. Liskov Subst it ut ion Principle ( LSP) Assum e t hat you have an inherit ance hierarchy wit h Person and Student. Wherever you can use Person, you should also be able t o use a Student, because Student is a subclass of Person. At first t his m ight sound like t hat 's always t he case aut om at ically, but when you st art t hinking about reflect ion ( reflect ion is a t echnique for being able t o program m at ically inspect t he t ype of an inst ance and read and set it s propert ies and fields and call it s m et hods, wit hout knowing about t he t ype beforehand) , for exam ple, it 's not so obvious anym ore. A m et hod t hat uses reflect ion for dealing wit h Person m ight not expect Student. The reflect ion problem is a synt act ical one. Mart in uses a m ore sem ant ical exam ple of Square t hat is a Rectangle. But when you use SetWidth() for t he Square, t hat doesn't m ake sense, or at least you have t o int ernally call SetHeight() as well. A pret t y different behavior from what Rectangle needs. Sure, all t hese principles are debat able in cert ain cont ext s. They should be used as a guide only, not as t he " only t rut h." For exam ple, OCP can easily be over- applied. You m ight have com e t o a point where you underst and bet t er how a m et hod should be im plem ent ed and want t o m odify it rat her t han ext end it . And adding a m et hod t o a class could also be seen as an ext ension.
Pat t erns are not only great wit h up- front design, t hey are very usable ( perhaps even m ore) during refact oring..." My code is j ust becom ing m essier and m essier. Ah, I need t o use t hat pat t ern! " I t 's like t he chicken or t he egg problem , but I decided t o st art wit h pat t erns in t his book and discuss refact oring aft er t hat ( in t he next chapt er) .
Is There Something to Look Out for Regarding Patterns?
Honest ly, I see lit t le reason for not learning about pat t erns, but t here is at least one very com m on negat ive effect t o look out for. What I 'm t hinking about is t hat for developers who have j ust learned about pat t erns, it 's very com m on t hat t hey feel com pelled t o squeeze in 17 pat t erns in each and every solut ion. Most oft en t hat effect won't st ay around for very long. What m ight st ay around for a lit t le longer is t he risk of over- design. I f not 17 pat t erns, t here m ight at least be a lot of t hought about how a problem should be solved. The init ial solut ion doesn't feel right because it doesn't use a cert ain pat t ern.
N ot e A friend of m ine t old m e about a recent design problem he discussed wit h som e developers at a com pany. I t t ook him t hree m inut es t o com e up wit h a very sim ple solut ion t o t he problem . ( The problem it self was a sim ple one.) Yet t he ot her developers weren't happy wit h t he solut ion so t hey spent t hree days of hard t hinking t o get it j ust right . Been t here, done t hat . I n m y opinion, Test - Driven Developm ent ( TDD) is a good t echnique for avoiding t hat over- design problem . The design focus will be m uch m ore on solving t he problem at hand and not hing else, and pat t erns will be int roduced when needed via refact oring.
You m ight get t he feeling t hat t he pat t erns concept is t he silver bullet . I t 's not of course not it 's j ust anot her t ool for t he t oolbox. Pat t erns are oft en perceived individually as being pret t y " sim ple," but t hey get very com plex in cont ext and com binat ion wit h ot her pat t erns. I don't rem em ber how m any t im es I 've heard people on t he newsgroups say som et hing like " I underst and pat t ern X, but when I t ry t o use it in m y applicat ion t oget her wit h pat t ern Y and pat t ern Z, it 's ext rem ely com plex. Please help! " That isn't really a reason not t o learn about pat t erns. This book will address t hat t o som e degree, but not here and now; first we will discuss som e pat t erns in isolat ion. I believe Joshua Kerievsky's Refact oring t o Pat t erns [ Kerievsky R2P] set s t his st raight by point ing out again and again t hat t he way t o m ost oft en use pat t erns isn't t o use t hem in up- front design, but t o refact or t oward or t o pat t erns.
Pattern Adoption Gregg I rwin said t his about pat t ern adopt ion: " For m e, m any concept s, like pat t erns, are learned in st ages:
1 . You use it wit hout being aware t hat you're using it 2 . You hear about it , read up on it , and t inker a bit 3 . You learn m ore and st art using it explicit ly, if naïvely 4 . You get t he fire and evangelize ( opt ional) 5 . Som et hing " clicks" 6 . You learn m ore and apply it " less naïvely" and m ore im plicit ly 7 . Tim e passes and you see flaws 8 . You quest ion t he concept ( oft en because you m isapplied it ) 9 . You eit her forget about it or add knowledge and experience ( Repeat st eps 59 as necessary) 1 0 . You use it wit hout being aware t hat you're using it "
OK, t im e t o m ove over t o t he first pat t ern cat egory. Let 's see if we can creat e an " Aha! " and not j ust a " Huh?" for t hose of you who are pat t ern newcom ers.
Design Patterns When I say Design Pat t erns here, t he first t hought s of m any will go t o t he Design Pat t erns book [ GoF Design Pat t erns] , which has been m ent ioned a whole bunch of t im es by now. I t 's by no m eans t he only book about Design Pat t erns, but it 's considered t he st andard work on t he subj ect . Design Pat t erns are abst ract and pret t y low- level in t hat t hey are t echnical and general in regard t o dom ain. I t doesn't m at t er what t ier or what t ype of syst em you are building, Design Pat t erns are st ill useful. One way t o describe Design Pat t erns is t hat t hey are about refining t he subsyst em s or com ponent s. You will find when we m ove t o t he ot her t wo cat egories I 'm about t o discuss here t hat it 's com m on t hat t he pat t erns t here use one or m ore Design Pat t erns or t hat som e specific Design Pat t erns can be applied in a m ore specific way in t hose cat egories.
N ot e While we're t alking about low- level, here is a fun lit t le st ory. I was asked what t he difference was bet ween m e and a friend on a professional level. I said t hat m y friend worked wit h low- level program m ing and I worked wit h high- level program m ing. The person asking didn't know anyt hing about program m ing, but he got t hat look on his face you get when list ening t o som eone blowing his own t rum pet way t oo m uch.
The Design Pat t erns book [ GoF Design Pat t erns] is pret t y hard- going. Each t im e I read it I underst and and learn m ore about it . For exam ple, I have oft en t hought , " That 's not correct " or " That 's not t he best solut ion" or even " That 's st upid." But aft er som e deliberat ion, I decide t hat t hey are " right " each t im e. So far t here has been a lot of t alk and lit t le act ion. I t 's t im e t o becom e concret e by giving an explanat ion of a Design Pat t ern. I have chosen one of m y favorit e Design Pat t erns called St at e, so here goes.
An Example: State Pattern Problem - based t eaching is a good pedagogic approach, so I 'll use it here. The following is a problem .
Problem A sales order can be in different st at es, such as " NewOrder," " Regist ered," " Grant ed," " Shipped," " I nvoiced," and " Cancelled." There are st rict rules concerning t o which st at es t he order can " go" from which st at es. For exam ple, it 's not allowed t o go direct ly from Regist ered t o Shipped. There are also differences in behavior depending upon t he st at es. For exam ple, when Cancelled, you can't call AddOrderLine() for adding m ore it em s t o t he order. ( That also goes for Shipped and I nvoiced, by t he way.)
One m ore t hing t o rem em ber is t hat cert ain behavior will lead t o st at e t ransform at ion. For exam ple, when AddOrderLine() is called, t he st at e t ransform s from Grant ed back t o New Order.
Solution Proposal One I n order t o solve t his problem , I need t o describe a st at e graph in code. I n Figure 2- 1 you find a very sim ple and classic st at e graph describing how st at e of t he but t on is changed bet ween Up and Down each t im e t he user pushes t he but t on.
Figu r e 2 - 1 . St a t e gr a ph for a bu t t on
I f we apply t his t echnique of a st at e graph on t he Order , it could look like Figure 2- 2 .
Figu r e 2 - 2 . St a t e gr a ph for a n Or de r [View full size image]
One obvious solut ion is probably t o use an enum like t his: public enum OrderState { NewOrder, Registered, Granted, Shipped,
Invoiced, Cancelled }
and t hen t o use a privat e field for t he current st at e in t he Order class, like t his: private OrderState _currentState = OrderState.NewOrder;
Then, in t he m et hods, you need t o deal wit h t wo t hings on t op of what t he m et hods should do. You m ust check if t he m et hod m ight be called at all in t hat st at e, and you need t o consider if a t ransit ion should t ake place and, if so, t o what new st at e. I t could look like t his in t he AddOrderLine() m et hod: private void AddOrderLine(OrderLine orderLine) { if (_currentState == OrderState.Registered || _currentState == OrderState.Granted) _currentState = OrderState.NewOrder; else if (_currentState == OrderState.NewOrder) //Don't do any transition. else throw new ApplicationException(... //Do the interesting stuff... }
As you saw in t he code snippet , t he m et hod got quit e a lot of unint erest ing code added j ust because of t aking care of t he st at e graph. An ugly if - st at em ent is very fragile t o changes in t he fut ure. Code sim ilar t o t hat will be sprinkled everywhere in t he Order class. What we do is spread knowledge of t he st at e graph in several different m et hods. This is a good exam ple of subt le but evil duplicat ion. Even for sim ple exam ples like t his, we should reduce t he code duplicat ion and fragm ent at ion. Let 's give it a t ry.
Solution Proposal Two Proposal Two is j ust a slight variat ion. You can have a privat e m et hod called _ChangeState() , which could, for exam ple, be called from AddOrderLine(). _ChangeState() could have a long swit ch st at em ent , like t his: private void _ChangeState(OrderState newState) { if (newState == _currentState) return; //Assume a transition to itself is not an error. switch (_currentState) { case OrderState.NewOrder: switch (newState) { case OrderState.Registered: case OrderState.Cancelled: _currentState = newState;
Break; default: throw new ApplicationException(... break; } case OrderState.Registered: switch (newState) { case OrderState.NewOrder: case OrderState.Granted: case OrderState.Cancelled: _currentState = newState; break; default: throw new ApplicationException(... break; } ... //And so on... } }
The AddOrderLine() now looks like t his: public void AddOrderLine(OrderLine orderLine) { _changeState(OrderState.NewOrder); //Do the interesting stuff... }
I was quit e lazy in t he previous code and only showed t he st art of t he st ruct ure of t he huge swit ch st at em ent , but I t hink it 's st ill pret t y obvious t hat t his is a good exam ple of sm elly code, especially if you consider t hat t his exam ple was sim plified and didn't discuss all aspect s or all st at es t hat were really needednot even close.
N ot e As always, for som e sit uat ions t he exam ple j ust shown was good enough and t he " right " solut ion. I j ust felt I had t o say t hat so as t o not im ply t hat t here is only one solut ion t o a problem t hat is always right . Also not e t hat I will discuss som e m ore about code sm ells in t he next chapt er.
OK, I 've been t here, done t hat in several proj ect s. I can't say I like t hat solut ion very m uch. I t seem s fine at first , but when t he problem grows, t he solut ion get s t roublesom e. Let 's t ry out anot her one.
Solution Proposal Three The t hird solut ion is based on a t able ( som e kind of configurat ion inform at ion) describing what should happen at cert ain st im uli. So inst ead of describing t he st at e t ransform at ion in code as in proposals one and t wo, t his t im e we describe t he t ransform at ions in a t able, Table 2- 1 . New Or der Regist er ed New Or der Cancelled Regist er ed New Or der Regist er ed Grant ed Regist er ed Cancelled ... ...
Ta ble 2 - 1 . St a t e Tr a n sit ion s Cu r r e n t St a t e
Allow e d N e w St a t e
Then your _ChangeState() m et hod can j ust check if t he new st at e t hat com es as a param et er is accept able for when t he current st at e is NewOrder, for exam ple. For t he current st at e NewOrder, only Regist ered and Cancelled are allowed as a new st at e. You could also add anot her colum n as shown in Table 2- 2 . New Or der Regist er ( ) Regist er ed New Or der Cancel( ) Cancelled Regist er ed
AddOrderLine( ) New Or der Regist er ed Grant ( ) Grant ed Regist er ed Cancel( ) Cancelled ...
...
Ta ble 2 - 2 . St a t e Tr a n sit ion s, Re vise d Cu r r e n t St a t e
M e t h od
N e w St a t e
Now your _ChangeState() m et hod shouldn't t ake t he new st at e as a param et er, but rat her t he m et hod nam e inst ead. Then _ChangeState() decides what t he new st at e should be by looking in t he t able. This is clean and sim ple. A big advant age here is t hat it 's very easy t o get an overview of t he different possible st at e t ransform at ions. The m ain problem is probably t hat it 's hard t o deal wit h cust om behavior depending upon t he current st at e and t hen t o go t o one st at e of several possible st at es when a m et hod execut es. Sure, it 's no harder t han wit h proposal t wo, but it 's st ill not very good. You could regist er inform at ion in t he t able about what delegat es ( a delegat e is like a st rongly t yped funct ion point er) should be execut ed at cert ain t ransform at ions, and you could probably ext end t hat idea t o solve t he ot her problem s as well, but I t hink t here is a risk t hat it get s a bit m essy during debugging, for exam ple. Do we have m ore ideas? Let 's apply som e knowledge reuse and t ry out t he Design Pat t ern called St at e.
Solution Proposal Four The general st ruct ure of t he St at e pat t ern is shown in Figure 2- 3 .
Figu r e 2 - 3 . St a t e pa t t e r n , ge n e r a l st r u ct u r e
The idea is t o encapsulat e t he different st at es as individual classes ( see ConcreteStateA and ConcreteStateB) . Those concret e st at e classes inherit from an abst ract State class. Context has a st at e inst ance as a field and calls Handle() of t he st at e inst ance when Context get s a Request() call. Handle() has different im plem ent at ions for t he different st at e classes. That 's t he general st ruct ure. Let 's see what t his could look like if we apply it t o t he problem at hand. I n Figure 2- 4 , you find a UML diagram for t he specific exam ple.
Figu r e 2 - 4 . St a t e pa t t e r n , spe cific e x a m ple [View full size image]
N ot e For t his exam ple, it m ight m ake sense t o add anot her abst ract class as t he base class for NewOrder , Registered , and Granted . The new class would im plem ent AddOrderLine() and Cancel() .
I n t he specific exam ple, t he Order class is t he Context from t he general st ruct ure. Again, Order has a field of OrderState , alt hough t his t im e OrderState isn't an enum , but a class. For t he sake of refact oring, your old t est s m ight expect an enum , and t hen you can keep t hat enum as well ( perhaps as a propert y which im plem ent at ion inspect s what is t he current inst ance in t he st at e inherit ance hierarchy) and t hereby not m ake changes t o t he ext ernal int erface. A newly creat ed Order get s a new st at e inst ance of a NewOrder at inst ant iat ion and sends it self t o t he const ruct or, like t his: internal OrderState _currentState = new NewOrder(this);
Not e t hat t he field is declared as int ernal. The reason for t his is so t hat t he st at e class can change t he current st at e by it self, so Order delegat es t he st at e t ransform at ions t ot ally t o t he different st at e classes. ( I could also let OrderState be an inner class of Order t o avoid t he need for internal .) This t im e, t he Register() m et hod on Order is ext rem ely sim ple. I t could look like t his: public void Register() { _currentState.Register(); }
The Register() m et hod on NewOrder is also pret t y sim ple. At least it can focus on it s own st at e, and t hat m akes t he code clean and clear. I t could look like t his: public void Register() { _parent._Register(); _parent._currentState = new Registered(_parent); }
Before changing t he st at e, t here was kind of a callback t o t he parent ( _parent._ Register() ) t elling it t o do it s t hing before t he st at e was changed. ( Not e t hat t he " callback" went t o t he int ernal m et hod _Register() and not t he public Register() .) This is j ust one exam ple of an opt ion, of course. Ot her exam ples would be t o put t he code in t he OrderState base class or in t he NewOrder class it self. I t should go wherever it 's best locat ed. As you saw, if I want t o do t hings before or aft er t he st at e t ransform at ion, it 's sim ple and very well encapsulat ed. I f I want t o disallow a cert ain t ransform at ion in t he NewOrder class, I j ust skip im plem ent ing t hat m et hod and use t he im plem ent at ion of t he base class OrderState for t hat m et hod. The im plem ent at ion of t he base class t hrows an except ion saying it was an illegal st at e t ransform at ion, if t hat 's t he want ed behavior. Anot her t ypical default im plem ent at ion is t o do not hing.
N ot e I f you need m ore cont ext - aware except ions, you can of course im plem ent t he m et hods in t he subclasses j ust as well, even if all t hey will do is raise except ions. This also im plies t hat inst ead of using a base class for OrderState you could use an int erface inst ead. I guess t hat if t he GoF book had been writ t en t oday, m any of t he pat t erns would have used int erfaces inst ead of ( or at least t oget her wit h) abst ract base
classes. The St at e pat t ern isn't t he m ost t ypical exam ple of t his, but it st ill is a possible exam ple.
More Comments When using t he St at e pat t ern, we were act ually swapping a single field int o a bunch of separat e classes. That doesn't sound like a very good idea at first , but what we t hen get is t he nice effect of m oving t he behavior t o where it belongs and good alignm ent t o t he Single Responsibilit y Principle ( SRP) . There are drawbacks, of course, and a t ypical one is t hat t he program can pot ent ially be flooded wit h sm all classes when we use a solut ion such as St at e. Which solut ion you prefer is indeed up for debat e, but I t hink t he St at e pat t ern is one t hat should be seriously considered here. You m ight find t hat it solves your problem wit h t he least am ount of duplicat ed code and wit h t he responsibilit y part it ioned out int o encapsulat ed and cohesive unit s, t he concret e st at e classes. But wat ch out t he St at e pat t ern is also very easy t o overuse, as is every t ool. Use it wisely! That was an exam ple of a Design Pat t ern, a generic one. We'll com e back t o m ore Design Pat t erns of anot her fam ily, but first a discussion about anot her cat egory of pat t erns.
Architectural Patterns A com m on associat ion t o t he t erm " Archit ect ural Pat t erns" is t o t hink about som e of t he pat t erns discussed in Buschm ann et al.'s Pat t ern- Orient ed Soft ware Archit ect ure [ POSA 1] . I n t hat book t here are a couple pat t erns gat hered under t he cat egory called Archit ect ural Pat t erns. Exam ples of t he pat t erns are Pipes and Filt ers and Reflect ion. Pipes and Filt ers are about channeling dat a t hrough Pipes and processing t he st ream in Filt ers. Pipes and Filt ers have been picked up by t he SOA com m unit y as a useful pat t ern for m essage- based syst em s. Reflect ion is built int o bot h Java and .NET, m aking it possible t o writ e program s t hat read from and writ e t o obj ect s in a generic way by only using t he m et adat a of t he obj ect s ( not knowing anyt hing about t he t ype of t he obj ect beforehand) . I f Design Pat t erns is about refining subsyst em s or com ponent s, Archit ect ural Pat t erns is about t he st ruct uring int o subsyst em s. To m ake it m ore concret e, let 's t ake a com m on exam ple, Layers [ POSA 1] .
An Example: Layers Layers or layering is a basic principle when it com es t o archit ect ure, which m eans t o fact or out responsibilit ies int o separat e cohesive unit s ( clust ers of classes) and define t he dependencies bet ween t hose unit s. Most developers are reasonably fam iliar wit h t his. Because it 's such a com m only used and well- underst ood pat t ern, we will j ust give a quick exam ple of it here t o give us a feeling of t he pat t ern cat egory.
Problem Assum e we have built a set of classes for a SalesOrder applicat ion, such as Customer, Order, Product, and so on. Those classes encapsulat e t he m eaning t hey have for t he Dom ain, and also how t hey are persist ed/ depersist ed and present ed. The current im plem ent at ion is t o persist t o t he file syst em and t o present t he obj ect s as HTML snippet s. Unfort unat ely, we now find out t hat we need t o be able t o consum e t he obj ect s as XML as well and t o use a relat ional dat abase for persist ence.
Solution Proposal One: Apply Layers The m ost com m on solut ion t o t his problem is probably t o fact or out som e of t he responsibilit ies of t he classes. The new requirem ent s m ade it obvious t o us t hat t he classes were clearly breaking t he SRP. We t ry t o split t he classes so t hat t he responsibilit ies are cohesively dealt wit h from a t echnological point of view.
Therefore, t he old classes will now only focus on t he dom ain m eaning ( let 's call t he layer Dom ain layer) . The present at ion ( or rat her consum pt ion) responsibilit ies are dealt wit h by anot her set of classes ( anot her layer called t he Consum er layer) , and t he persist ence responsibilit ies are dealt wit h by yet anot her set of classes ( t he Persist ence layer) . Three layers spont aneously felt like a t ypical solut ion here, one per responsibilit y cat egory. The t wo new layers have t wo im plem ent at ions each for dealing wit h bot h t he old requirem ent s and t he new. The dependencies bet ween t he layers are also defined, and in t his case we decided t hat t he consum pt ion layer will depend on t he dom ain layer, and t he dom ain layer will depend on t he persist ence layer. That way, t he Consum er layer is t ot ally unaware of t he Persist ence layer, which was som et hing we decided was good in t his part icular proj ect . We will get back t o t he subj ect of layering again in Chapt er 4, " A New Default Archit ect ure," and t hen approach it a bit different ly.
Another Example: Domain Model Pattern We have already discussed t he Dom ain Model pat t ern [ Fowler PoEAA] in Chapt er 1 ( and will do so m uch m ore t hroughout t he book) . I t hink about t he Dom ain Model pat t ern as an exam ple of an Archit ect ural Pat t ern. We're not going t o discuss an exam ple about t he Dom ain Model pat t ern here and now because t he whole book is about j ust t hat . I nst ead, we'll cont inue by focusing on anot her dim ension of t he pat t erns, regarding dom ain- dependence or not . Next up are Design Pat t erns for specific t ypes of applicat ions.
Design Patterns for Specific Types of Applications Anot her set of Design Pat t erns isn't as generic as t hose discussed so far, but , for exam ple, pat t erns for building ent erprise applicat ions. Defining an ent erprise applicat ion is t ricky, but you can t hink of it as a large- scale inform at ion syst em wit h m any users and/ or a lot of dat a. The m ain book dealing wit h t he pat t erns in t his cat egory is Mart in Fowler's Pat t erns of Ent erprise Applicat ion Archit ect ure [ Fowler PoEAA] . The pat t erns here at first sight m ight not seem as cool or am azing as som e of t he Design Pat t erns, but t hey are ext rem ely useful, cover a lot of ground, and cont ain a lot of experience and knowledge. As I said, t hey are less generic t han t he ot her Design Pat t erns and focused j ust on large- scale inform at ion syst em s. They com e int o play for t he chosen st ruct ure of t he logic; for exam ple, t he Dom ain Model. The pat t erns here aren't so m uch about how t he Dom ain Model it self ( or any of t he ot her m odels for st ruct uring t he m ain logic) should be st ruct ured, but m ore about t he infrast ruct ure for support ing t he Dom ain Model. To m ake it m ore concret e, I 'd like t o discuss an exam ple, and I choose Query Obj ect s [ Fowler PoEAA] .
An Example: Query Objects Let 's assum e for a m om ent t hat you have a Dom ain Model for a SalesOrder applicat ion. There is a Customer class and an Order class, and t he Order class in part icular is com posed of a num ber of ot her classes. This is sim ple and clear. There are several different solut ions from which t o choose in order t o navigat e t he Dom ain Model. One solut ion is t o have a global root obj ect t hat has references t o root - like collect ions. I n t his case, a cust om er collect ion would be an exam ple of one of t hese. So what t he developer does is t o st art wit h t he global root obj ect and navigat e from t here t o t he cust om er collect ion, and t hen it erat e t he collect ion unt il what is needed is found, or perhaps navigat e t o t he cust om er's sales orders if t hat 's what 's of int erest . A sim ilar paradigm is t hat all collect ions are global so you can direct ly access t he cust om er collect ion and it erat e over it . Bot h t hose paradigm s are easy t o underst and and sim ple t o use, but one drawback is t hat t hey are lacking som ewhat from t he perspect ive of a dist ribut ed syst em . Assum e you have t he Dom ain Model running at t he client ( each client has one Dom ain Model, or rat her a sm all subset of t he Dom ain Model inst ances, and no shared Dom ain Model inst ances) and t he dat abase is running at a dat abase server ( a pret t y com m on deploym ent m odel) . What should happen when you ask t he root obj ect t o get t he cust om er collect ion of one m illion cust om ers? You can get all t he cust om ers back t o t he client so t he client can it erat e over t he collect ion locally. Not so nice t o wait for t hat huge collect ion t o be t ransm it t ed. Anot her opt ion is t o add an applicat ion server t o t he pict ure and ask it t o only send over a collect ion
reference t o t he client side, and t hen m uch less dat a is t ransm it t ed, of course. On t he ot her hand, t here will be an incredible am ount of net work calls when t he client is it erat ing t he list and asking for t he next cust om er over t he net work one m illion t im es. ( I t will be even worse if t he cust om er inst ances t hem selves aren't m arshaled by value but only by reference.) Yet anot her opt ion is t o page t he cust om er collect ion so t he client perhaps get s 100 cust om ers from t he server at a t im e. I knowall t hese solut ions have one problem in com m on; you don't oft en want t o look at all t he cust om ers. You need a subset , in which case it 's t im e t o discuss t he next problem .
Problem The problem is t hat t he users want a form where t hey can search for cust om ers flexibly. They want t o be able t o ask for all cust om ers who Have a nam e wit h " aa" in it . ( Hidden m arket ing for a Swedish car com pany.) Ordered som et hing last m ont h. Have orders wit h a t ot al am ount great er t han one m illion. Have a reference person called " St ig." But on t he sam e form , t hey should also be able t o ask for j ust cust om ers in a cert ain part of Sweden. Again, t he search form needs t o be pret t y flexible. I 'm going t o discuss t hree different solut ion proposals, nam ely " filt ering wit hin Dom ain Model," " filt ering in dat abase wit h huge param et er list s," and " Query Obj ect s."
Solution Proposal One: Filter Within Domain Model Let 's t ake a st ep back and adm it t hat we could use any of t he solut ions already discussed so t hat t he collect ion is m at erialized som ewhere and t hen t he filt er is checked for every inst ance. All inst ances m eet ing t he filt er crit eria are added t o a new collect ion, and t hat is t he result . This is a pret t y sim ple solut ion, but pract ically unusable in m any real- world sit uat ions. You will wast e space and t im e. Not only were t here one m illion cust om ers, but you also had t o m at erialize t he orders for t he cust om ers. Phew, t hat solut ion is j ust im possible t o use and it 's even worse when you scale up t he problem .... Of course, t he conclusion here depends t o a large degree on t he execut ion plat form . Rem em ber what I said about t he deploym ent m odela subset of t he Dom ain Model inst ances in each client , t he dat abase at a dat abase server, no shared Dom ain Model inst ances. I f inst ead t here was one shared set of Dom ain Model inst ances at an applicat ion server ( which has it s own problem sm ore about t hat in lat er chapt ers) , t his m ight have been a suit able solut ion, but only for server- side logic. For client s asking for a subset of t he shared Dom ain Model inst ances, t he client s m ust express t heir crit eria som ehow.
Solution Proposal Two: Filtering in Database with Huge Parameter Lists Dat abases are norm ally good at st oring and querying, so let 's use t hem t o our advant age here. We j ust need t o express what we want wit h a SQL st at em ent and t hen t ransform t he result int o inst ances
in our Dom ain Model. A SQL st at em ent like t he following could solve t he first problem :
SELECT Id, CustomerName, ... FROM Customers WHERE CustomerName LIKE '%aa%' AND Id IN (SELECT CustomerId FROM ReferencePersons WHERE FirstName = 'Stig') AND Id IN (SELECT CustomerId FROM Orders WHERE TotalAmount > 1000000) AND Id IN (SELECT CustomerId FROM Orders WHERE OrderDate BETWEEN '20040601' AND '20040630')
N ot e I t 's debat able whet her I can com bine t he t wo subselect s t arget ing Orders int o a single subselect . As t he requirem ent was st at ed, I don't t hink so ( because t he m eaning would change slight ly if I com bined t hem ) . Anyway, t his isn't really im port ant for t he discussion here.
Here we j ust m at erialize t he inst ances t hat are of int erest t o us. However, we probably don't want t he layer cont aining t he Dom ain Model t o have t o cont ain all t hat SQL code. What 's t he point of t he Dom ain Model in t hat case? The consum er layer j ust get s t wo m odels t o deal wit h. So we now have a new problem . How shall t he consum er layer express what it want s? Ah, t he Dom ain Layer which is responsible for t he m apping bet ween t he dat abase and Dom ain Model can provide t he consum er layer wit h a search m et hod. Proposal num ber t wo is t he following: public IList SearchForCustomers (string customerNameWithWildCards , bool mustHaveOrderedSomethingLastMonth , int minimumOrderAmount , string firstNameOfAtLeastoneReferencePerson)
This probably solves t he requirem ent for t he first query, but not t he second. We need t o add a few m ore param et ers like t his: public IList SearchForCustomers (string customerNameWithWildCards , bool mustHaveOrderedSomethingLastMonth , int minimumOrderAmount , string firstNameOfAtLeastoneReferencePerson
, string country, string town)
Do you see where t his is going? The param et er list quickly get s im pract ical because t here are probably a whole bunch of ot her param et ers t hat are also needed. Sure, edit ors showing placeholders for each param et er helps when calling t he m et hod, but using t he m et hod will st ill be error- prone and im pract ical. And when anot her param et er is needed, you have t o go and change all t he old calls, or at least provide a new overload. Anot her problem is how t o express cert ain t hings in t hat pret t y powerless way of prim it ive dat at ypes in a list of param et ers. A good exam ple of t hat is t he param et er called mustHaveOrderedSomethingLastMonth . What about t he m ont h before t hat ? Or last year? Sure, we could use t wo dat es inst ead as param et ers and m ove t he responsibilit y of defining t he int erval t o t he consum er of t he m et hod, but what about when we only care about cust om ers in a cert ain t own? What should t he dat e param et ers be t hen? I guess I could use m inim um and m axim um dat es t o creat e t he biggest possible int erval, but it 's not ext rem ely int uit ive t hat t hat 's t he way t o express " all dat es."
N ot e Gregory Young com m ent ed on t he problem of how t o express presendence. Expressing t his wit h a param et er list is t roublesom e: ( crit erion1 and crit erion2) or ( crit erion1 and crit erion2)
I t hink we have quickly grown out of t his solut ion, t oo. I cam e t o t he sam e conclusion back in t he VB6 days, so I used an array- based solut ion. The first colum n of t he array was t he fieldnam e ( such as Cust om erNam e) , t he second colum n was t he operat or ( such as Like from an enum erat or) and t he t hird colum n was t he crit erion such as " * aa* " . Each crit erion had one row in t he array. That solut ion solved som e of t he problem s wit h t he param et er list , but it had it s own problem s. Just because t here was a new possible crit erion added, I didn't have t o change any of t he old consum er code. That was good, but it was pret t y powerless for advanced crit erion, so I st epped back and exposed t he dat abase schem a, for exam ple, t o deal wit h t he crit erion " Have any orders wit h a t ot al am ount larger t han one m illion?" I t hen used t he com plet e I N- clause as t he crit erion. The array- based solut ion was a st ep in t he right direct ion, but it would have becom e a lit t le m ore flexible wit h obj ect s inst ead. Unfort unat ely, it wasn't really possible t o writ e m arshal by value com ponent s in VB6. There were solut ions t o t he problem , such as using a m ore flexible array st ruct ure, but t he whole t hing is so m uch m ore nat ural in .NET. Over t o t he Query Obj ect pat t ern.
Solution Proposal Three: Query Objects The idea of t he Query Obj ect pat t ern is t o encapsulat e t he crit eria in a Query inst ance and t hen send t hat Query inst ance t o anot her layer where it is t ranslat ed int o t he required SQL. The UML diagram for t he general solut ion could look like t hat shown in Figure 2- 5 .
Figu r e 2 - 5 . Cla ss dia gr a m for ge n e r a l Qu e r y Obj e ct solu t ion
The crit erion could use anot her query ( even t hough it 's not apparent in t he t ypical descript ion of t his as in Figure 2- 5 ) , and t hat way it 's easy t o creat e t he equivalent of a subquery in SQL. Let 's com e up wit h a t ry for a Query Obj ect language for applying on t he problem . First t hough, let 's assum e t hat t he Dom ain Model is as is shown in Figure 2- 6 .
Figu r e 2 - 6 . D om a in M ode l t o be u se d for t h e e x a m ple
Let 's see what it could look like our newly creat ed naïve query language in C# : Query q = new Query("Customer"); q.AddCriterion("CustomerName", Op.Like, "*aa*"); Query sub1 = new Query("Order"); sub1.AddCriterion("TotalAmount", Op.GreaterThan, 1000000); q.AddCriterion(sub1); Query sub2 = new Query("Order"); sub2.AddCriterion("OrderDate", Op.Between, DateTime.Parse("2004-06-01"), DateTime.Parse("2004-06-30")); q.AddCriterion(sub2); q.AddCriterion("ReferencePersons.FirstName", Op.Equal, "Stig");
N ot e
The param et er t o t he Query const ruct or is not a t able nam e but a Dom ain Model classnam e. The sam e goes for t he param et ers t o AddCriterion(); I m ean it 's not t able colum ns, but class fields/ propert ies. I n t hat case, propert y nam es or field nam es are used in t he Dom ain Model. Also not e t hat in t his specific exam ple, I didn't need a subquery for t he crit erion regarding t he ReferencePersons because t he Dom ain Model was navigable from Customer t o ReferencePerson. On t he ot her hand, subqueries were needed for t he Orders for t he opposit e reason.
More Comments I f you are SQL- lit erat e, your first im pression m ight be t hat t he SQL- version was m ore expressive, easier t o read, and j ust bet t er. SQL is cert ainly a powerful query language, but rem em ber what we want t o accom plish. We want t o be able t o work as m uch as possible wit h t he Dom ain Model ( wit hin lim it s) and t hereby achieve a m ore m aint ainable solut ion. Also not e t hat t he C# code j ust shown was needlessly t alkat ive. Lat er on in t he book we will discuss how t he synt ax could look by writ ing a t hin layer on t op of a general query obj ect im plem ent at ion. So what we gained was furt her t ransparence of our code wit h regard t o t he dat abase schem a. Generally, I t hink t his is a good t hing. When we really need t o, we can always go out of t his lit t le sandbox of ours t o st at e SQL queries wit h t he full power of t he dat abase and wit hout a lifeline. Anot her t hing I 'd like t o point out is t hat creat ing a com pet ent Query Obj ect im plem ent at ion will quickly becom e very com plex, so wat ch out t hat you don't t ake on t oo m uch work. A nice lit t le side effect is t hat you can also use query obj ect s pret t y easily for local filt ering, such as holding on t o a cached list of all product s. For t he developer consum ing t he Dom ain Model, he or she j ust creat es a Query Obj ect as usual, but it is t hen used in a slight ly different m anner, wit hout t ouching t he dat abase.
W a r n in g I know, I know. Caching is j ust as cool and useful as it is dangerous. Wat ch out , it can backfire. You have been warned.
Som e DDD- lit erat e readers would probably prefer t he Specificat ion pat t ern [ Evans DDD] as t he solut ion t o t his problem . That provides a neat connect ion over t o t he t hird and final pat t ern cat egory we are going t o focus on: Dom ain Pat t erns.
Domain Patterns Dom ain Pat t erns have a very different focus from t he Design Pat t erns and t he Archit ect ural Pat t erns. The focus is t ot ally on how t o st ruct ure t he Dom ain Model it self, how t o encapsulat e t he dom ain knowledge in t he m odel, and how t o apply t he ubiquit ous language and not let t he infrast ruct ure dist ract away from t he im port ant part s. There is som e overlap wit h Design Pat t erns, such as t he Design Pat t ern called St rat egy, [ GoF Design Pat t erns] which is considered t o be a Dom ain Pat t ern as well. The reason for t he overlap is t hat pat t erns such as St rat egy are very good t ools for st ruct uring t he Dom ain Model. As Design Pat t erns, t hey are t echnical and general. As Dom ain Pat t erns, t hey focus on t he very core of t he Dom ain Model. They are about m aking t he Dom ain Model clearer, m ore expressive, and purposeful, as well as let t ing t he knowledge gained of t he dom ain be apparent in t he Dom ain Model. When I ended t he previous sect ion, I m ent ioned t hat t he Specificat ion pat t ern as a Dom ain Pat t ern is an alt ernat ive t o Query Obj ect s pat t ern. I t hink t hat 's a good way of explaining how I see what Dom ain Pat t erns are. Query Obj ect s is a t echnical pat t ern where t he consum er can define a query wit h a synt ax based on obj ect s, for finding m ore or less any of t he obj ect s in t he Dom ain Model. The Specificat ion pat t ern can be used for querying as well, but inst ead of using a generic query obj ect and set t ing crit eria, one by one, a specificat ion is used as a concept t hat it self encapsulat es dom ain knowledge and com m unicat es t he purpose. For exam ple, for finding t he gold cust om ers, you can use bot h query obj ect s and specificat ions, but t he solut ions will differ. Wit h Query Obj ect s, you will express crit eria about how you define gold cust om ers. Wit h a Specificat ion, you will have a class t hat is perhaps called GoldCustomerSpecification. The crit eria it self isn't revealed or duplicat ed in t hat case, but encapsulat ed in t hat class wit h a well- describing nam e. One source of Dom ain Pat t erns is [ Arlow/ Neust adt Archet ype Pat t erns] , but I have chosen a Dom ain Pat t ern- exam ple from anot her good source, Eric Evans' book Dom ain Driven Design [ Evans DDD] . The chosen exam ple pat t ern is called Fact ory.
N ot e Pat t ern- aware readers m ight get confused because I t alk about t he Dom ain Pat t ern Fact ory here and t he Design Pat t erns book [ GoF Design Pat t erns] also has som e Fact ory pat t erns. Again, t he focus of Design Pat t erns is m ore on a t echnical level and t he focus of Dom ain Pat t erns is on a sem ant ic Dom ain Model level. They are also different regarding t he det ailed int ent s and t ypical im plem ent at ions. The Fact ory pat t erns of GoF are called Fact ory Met hod and Abst ract Fact ory. Fact ory Met hod is about deferring inst ant iat ion of t he " right " class t o subclasses. Abst ract Fact ory is about creat ing fam ilies of dependent obj ect s. The Fact ory pat t ern of DDD is st raight forward im plem ent at ion- wise and is only about capt uring and encapsulat ing t he creat ion concept for cert ain classes.
An Example: Factory Who said t hat t he soft ware indust ry is influenced by indust rialism ? I t 's debat able whet her it 's good, but it is influenced. We t alk about engineering as a good principle for soft ware developm ent ; we t alk about archit ect ure, product lines and so on and so fort h. Here is anot her such influence, t he Fact ory pat t ern. But first , let 's st at e t he problem t hat goes wit h t his exam ple.
Problem The problem t his t im e is t hat t he const ruct ion of an order is com plex. I t needs t o be done in t wo very different flavors. The first is when a new order is creat ed t hat is unknown t o t he dat abase. The second is when t he consum er asks for an old order t o be m at erialized int o t he Dom ain Model from t he dat abase. I n bot h cases, t here needs t o be an order inst ance creat ed, but t he sim ilarit y ends t here as far as t he const ruct ion goes. Anot her part of t he problem is t hat an order should always have a cust om er; ot herwise, creat ing t he order j ust doesn't m ake sense. Yet anot her part of t he problem is t hat we need t o be able t o creat e new credit orders and repeat ed orders.
Solution Proposal One The sim plest solut ion t o t he problem is t o j ust use a public const ruct or like t his: public Order()
Then, aft er having called t his const ruct or, t he consum er has t o set up t he propert ies of t he inst ance t he way it should be t o be insert ed or by asking t he dat abase for t he values. Unfort unat ely, t his is like opening a can of worm s. For exam ple, we m ight have dirt y t racking on propert ies, and we probably don't want t he dirt y t racking t o signal an inst ance t hat was j ust reconst it ut ed from persist ence as dirt y. Anot her problem is how t o set t he ident ificat ion, which is probably not set t able at all. Reflect ion can solve bot h problem s ( at least if t he ident ifier isn't declared as readonly ) , but is t hat som et hing t he Dom ain Model consum er developer should have t o care about ? I definit ely don't t hink so. There are som e m ore esot eric solut ions we could explore, but I 'm sure m ost of you would agree t hat a t ypical and obvious solut ion would be t o use param et erized const ruct ors inst ead. Because I have spent a lot of program m ing t im e in t he past a long t im e ago wit h VB6, I haven't been spoiled by param et erized const ruct ors. Can you believe t hat not having param et erized const ruct ors? I 'm act ually having a hard t im e believing it m yself. Anyway, in C# and Java and so on, we do have t he possibilit y of param et erized const ruct ors, and t hat is probably t he first solut ion t o consider in dealing wit h t he problem . So let 's use t hree public const ruct ors of t he Order class: public Order(Customer customer); public Order(Order order, bool trueForCreditFalseForRepeat); public Order(int orderId);
The first t wo const ruct ors are used when creat ing a new inst ance of an Order t hat isn't in t he dat abase yet . The first of t hem is for creat ing a new, ordinary Order. So far, so good, but I have delayed int roducing requirem ent s on purpose, m aking it possible t o creat e Orders t hat st art as reservat ions. When t hat requirem ent is added, t he first const ruct or will have t o change t o be possible t o use for t wo different purposes. The second const ruct or is for creat ing eit her a credit Order or a repet it ion of an old Order. This is definit ely less clear t han I would like it t o be. The last const ruct or is used when fet ching an old Order, but t he only t hing t hat reveals which const ruct or t o use is t he param et er. This is not clear. Anot her problem ( especially wit h t he t hird const ruct or) is t hat it 's considered bad pract ice t o have lot s of processing in t he const ruct or. A j um p t o t he dat abase feels very bad.
Solution Proposal Two According t o t he book Effect ive Java [ Bloch Effect ive Java] , t he first it em ( best pract ice) out of 57 is t o consider providing st at ic Fact ory m et hods inst ead of const ruct ors. I t could look like t his: public public public public public
static static static static static
Order Order Order Order Order
Create(Customer customer); CreateReservation(Customer customer); CreateCredit(Order orderToCredit); CreateRepeat(Order orderToRepeat); Get(int orderId);
A nice t hing about such a Fact ory m et hod is t hat it has a nam e, revealing it s int ent ion. For exam ple, t he fift h m et hod is a lot clearer t han it s const ruct or count erpart from solut ion 1, const ruct or t hree, right ? I act ually t hink t hat 's t he case for all t he previous Fact ory m et hods when com pared t o solut ion 1, and now I added t he requirem ent of reservat ions and repeat ing orders wit hout get t ing int o const ruct ion problem s. Bloch also discusses t hat st at ic Fact ory m et hods don't have t o creat e a new inst ance each t im e t hey get involved, which m ight be big advant age. Anot her advant age, and a m ore t ypical one, is t hat t hey can ret urn an inst ance of any subt ype of t heir ret urn t ype. Are t here any drawbacks? I t hink t he m ain one is t hat I 'm probably violat ing t he SRP [ Mart in PPP] when I have m y creat ional code in t he class it self. Evans' book [ Evans DDD] is a good rem inder of t hat where he uses a m et aphor of a car engine. The car engine it self doesn't know how it is creat ed; t hat 's not it s responsibilit y. I m agine how m uch m ore com plex t he engine would have t o be if it not only had t o operat e but also had t o creat e it self first . This argum ent is especially valid in cases where t he creat ional code is com plex. Add t o t hat m et aphor t he elem ent t hat t he engine could also be fet ched from anot her locat ion, such as from t he shelf of a local or a cent ral st ock; t hat is, an old inst ance should be reconst it ut ed by fet ching it from t he dat abase and m at erializing it . This is t ot ally different from creat ing t he inst ance in t he first place, bot h for a real, physical engine and for an Order inst ance in soft ware. We are close t o t he pat t ern solut ion now. Let 's use a solut ion sim ilar t o t he second proposal, but fact or out t he creat ional behavior int o a class of it s own, forget t ing about t he " fet ch from dat abase" for now ( which is dealt wit h by anot her Dom ain Pat t ern called Reposit ory, which we will discuss a lot in lat er chapt ers) .
Solution Proposal Three
So now we have com e t o using t he Fact ory pat t ern as t he Dom ain Pat t ern. Let 's st art wit h a diagram , found in Figure 2- 7 .
Figu r e 2 - 7 . An in st a n ce dia gr a m for t h e Fa ct or y pa t t e r n
The code could look like t his from a consum er perspect ive t o obt ain a ready- m ade new Order inst ance: anOrder = OrderFactory.Create(aCustomer); aReservation = OrderFactory.CreateReservation(aCustomer); aCredit = OrderFactory.CreateCredit(anOldOrder); aRepeat = OrderFactory.CreateRepeat(anOldOrder);
This is not m uch harder for t he consum er t han it is using an ordinary const ruct or. I t 's a lit t le bit m ore int rusive, but not m uch. I n order for t he consum er t o get t o an old Order, t he consum er m ust t alk t o som et hing else ( not t he Fact ory, but a Reposit ory) . That 's clearer and expressive, but it 's anot her st ory for lat er on. To avoid t he inst ant iat ion of orders via t he const ruct or from ot her classes in t he Dom ain Model if t he Fact ory code is in an ext ernal class is not possible, but you can m ake it a lit t le less of a problem by m aking t he const ruct or int ernal, and hopefully because t he Fact ory is t here, t he Dom ain Model developers t hem selves underst and t hat t hat 's t he way of inst ant iat ing t he class and not using t he const ruct or of t he t arget class direct ly.
N ot e Eric Evans com m ent ed on t he previous paragraph wit h t he following: " I hope som eday languages will support concept s like t his. ( Just as t hey have const ruct ors now, perhaps t hey will allow us t o declare fact ories, et c.) "
More Comments First of all, please not e t hat som et im es a const ruct or is j ust what we want . For exam ple, t here m ight not be any int erest ing hierarchy, t he client want s t o choose t he im plem ent at ion, t he const ruct ion is very sim ple, and t he client will have access t o all propert ies. I t 's im port ant t o underst and not t o j ust go on and creat e fact ories t o creat e each and every inst ance, but t o use fact ories where t hey help you out and add clarit y t o and reveal t he int ent ion of t he Dom ain Model.
What is t ypical of t he Fact ory is t hat it set s up t he inst ance in a valid st at e. One t hing I have becom e pret t y fond of doing is set t ing up t he sub- inst ances wit h Null Obj ect s [ Woolf Null Obj ect ] if t hat 's appropriat e. Take an Order, for exam ple. Assum e t hat t he shipm ent of an Order is t aken care of by a transporter. At first , t he Order hasn't been shipped ( and in t his case we have not t hought m uch about shipm ent at all) , so we can't give it any transporter obj ect , but inst ead of j ust leaving t he TRansporter propert y as null, I set t he propert y t o t he em pt y ( " not chosen," perhaps) transporter inst ead. I n t his way, I can always expect t o find a descript ion for t he TRansporter propert y like t his: anOrder.Transporter.Description
I f TRansporter had been null, I would have needed t o check for t hat first . The Fact ory is a very handy solut ion for t hings like t his. ( You could do t he sam e t hing in t he case of a const ruct or, but t here m ight be m any places you need t o apply Null Obj ect s, and it m ight not be t rivial t o decide on what t o use for Null Obj ect s if t here are opt ions. What I 'm get t ing at is t hat t he com plexit y of t he const ruct or increases.) You can, of course, have several different Fact ory m et hods and let t hem t ake m any param et ers so you get good cont rol of t he creat ion from t he out side of t he Fact ory. Using Fact ories can also hide infrast ruct ure requirem ent s t hat you can't avoid. I t 's ext rem ely com m on t o hear about Fact ories and see t hem used in a less sem ant ic way, or at least different ly so t hat all inst ances ( new or " old" ) are creat ed wit h Fact ories. I n COM for exam ple, every class has t o have a class fact ory, as do m any fram eworks. Then it 's not t he Dom ain Pat t ern Fact ory t hat is used: sim ilar in nam e and in t echnique, different in int ent ion. Anot her exam ple is t hat you can ( indirect ly) let t he Fact ory go t o t he dat abase t o fet ch default values. Again, it 's good pract ice t o not have m uch processing in const ruct ors, so t hey are not a good place t o have logic like t hat .
N ot e Now we have a problem . I t shouldn't be possible t o set som e propert ies from t he out side; however, we need t o get t he values t here. This is done when a Fact ory set s default values ( and when a Reposit ory fet ches an old inst ance from t he dat abase) . Perhaps t his feels bad t o you? We'll get back t o t his lat er, but for now, rem em ber t hat t his is oft en a problem anyway because reflect ion can be used for changing t he inner st at e, at least if t hat is allowed according t o t he securit y set t ings. My philosophy on t his is t hat you can't fully st op t he program m ers from doing evil when consum ing t he Dom ain Model, but you should m ake it hard t o do st upid t hings by m ist ake, and you need t o check t hat evil t hings haven't been done. All in all, I don't t hink t his is usually a real problem .
Overall, I t hink t he usage of t he Fact ory pat t ern clearly dem onst rat ed t hat som e inst ant iat ion com plexit y was m oved from t he Order int o a concept of it s own. This also helped t he clarit y of t he
Dom ain Model t o som e degree. I t 's a good clue t hat t he inst ant iat ion logic is int erest ing and com plex.
Summary This was a quick int roduct ion and m y at t em pt t o get you int erest ed in pat t erns. Hopefully it worked, because I will use pat t erns as an im port ant t ool in t he following chapt ers. Now it 's t im e t o dive int o how t o use TDD by discussing som e exam ples.
Chapter 3. TDD and Refactoring A good exam ple is a powerful t hing; I t hink we are in agreem ent on t hat . One way t o describe Test Driven Developm ent ( TDD) is t o say t hat you use t est s or exam ples for specifying t he behavior. Those exam ples will serve you over and over again for m any different purposes. They will serve you during developm ent , for exam ple, for finding lacking or incorrect requirem ent s. When you refact or, t he t est s will be a safet y net . Aft er developm ent t hey will serve you as qualit y docum ent at ion. I n t his chapt er we will not j ust t alk about t est s as exam ples; we will discuss TDD it self wit h som e exam ples. I j ust t hink t hat 's a good way of describing what it 's all about . Aft er som e init ial discussion of TDD and t he st at e- based st yle, m y friend Claudio Perrone will discuss som e t echniques for creat ing exam ples t he int eract ion- based way, writ ing t est s by using st ubs and m ocks. A key principle t hat is used during TDD is refact oring. Even refact oring is pret t y exam ple- cent ric. You can t hink of it as writ ing a first exam ple of a piece of t he solut ion, t hen using refact oring t o refine t hat exam ple unt il you're done. So you don't have t o com e up wit h a perfect design up front . That is good news, because it is close t o im possible anyway. Again, we will discuss refact oring wit h som e exam ples, of course. Let 's st art wit h som e basics about TDD.
Test-Driven Development (TDD) Aft er having used TDD for a couple of years now, I 'm st ill get t ing even fonder of t he t echnique. I 'm becom ing m ore and m ore convinced t hat TDD is t he single m ost im port ant t echnique in becom ing a bet t er program m er. St rong words any subst ance? You'll have t o t ry it out on your own t o know for yourself, of course, but let 's have a look at what it 's all about . Let 's st art wit h a short replet ion from Chapt er 1, " Values t o Value," about t he flow.
The TDD Flow First , you st art writ ing a t est . You m ake t he t est fail m eaningfully so t hat you can be sure t hat t he t est is t est ing what you t hink it should. The second st ep is t o writ e t he sim plest possible code t hat m akes t he t est pass. The t hird st ep is t o refact or. Then you st art all over again, adding anot her t est . I f we use xUnit lingo, t he first st ep should give you a red light / bar, and t he second st ep should give you a green light / bar. ( Unfort unat ely, com pilat ion errors and refact oring don't have colors yet .) So t he m ant ra is Red, Green, Refact or. Red, Green, Refact or...
Time for a Demo I 'm assum ing t hat you are fam iliar wit h Test driven.net [ Test driven.net ] , NUnit [ NUnit ] , or som e ot her sim ilar t ool ( or pick up som e such t ool now and learn t he basics on your own) . So inst ead of a t ool discussion, I 'll focus on how t o apply t he concept of TDD. I know, I know. There are an enorm ous num ber of good dem onst rat ion t ext s on how t o use TDD, such as [ Beck TDD] , [ Ast els TDD] , [ Mart in PPP] . Anyway, I 'd like t o have a go at dem onst rat ing it m yself. Rat her t han doing what is m ost com m on, I won't use a hom em ade t oy exam ple, but I 'll use an exam ple from a real- world applicat ion of m ine. A few years ago, I was asked by Dynapac t o writ e an applicat ion t hat t hey could bundle wit h a new line of planers t hat t hey produced. The planers are used for rem oving old asphalt before paving out new. Asphalt m illing is used t o rest ore t he surface of asphalt pavem ent t o a specified profile. Bum ps, rut s, and ot her surface irregularit ies are rem oved, leaving a uniform , t ext ured surface. The applicat ion would be used for calculat ing a num ber of different t hings, helping t o opt im ize t he usage of t he planers. The applicat ion needed t o be able t o calculat e t he hardness of t he old asphalt , t he t im e needed t o plane a cert ain proj ect , t he t rucks needed t o keep m illing wit h t he m inim um cost , and lot s of ot her t hings. I 'm going t o use t his real- world applicat ion t o dem onst rat e how TDD can be used so you get a feeling for t he flow. I 'm going t o focus on calculat ing t he num ber of t rucks. I t is im port ant t o have t he correct num ber of t rucks for t ransport ing t he old asphalt . I f you have t oo
few, t he planer will have t o st op every now and t hen or m ove slower. I f you have t oo m any, it 's a wast e of t rucks and personnel. I n bot h cases, t he cost will increase for t he proj ect . OK, let 's do t his t he TDD way. I 'll st art out by writ ing t he ident ified needed funct ionalit y in a t ext file like t his: Calculate millability Calculate milling capacity Calculate number of trucks needed
As I said, I 'm going t o focus on t he calculat ion for t he num ber of t rucks now, so I put a m arker in t he file on t hat line so I know what I decided t o st art wit h. As soon as I ( or m ore likely t he cust om er) com e up wit h anot her piece of funct ionalit y t hat I don't want t o work on right now, I add it t o t he file. So t hat t ext file will help m e t o not forget about anyt hing, while st ill enabling m e t o focus on one t hing at a t im e.
N ot e Test s and refact orings could be writ t en t o t he sam e file, but I prefer not t o. I nst ead I writ e t est s t agged wit h t he Ignore at t ribut e for t est s t hat I don't want t o focus on now. Refact oring needs t hat I find, but I don't want t o deal wit h now, are probably not wort h being done, or t hey will be found again and dealt wit h t hen.
The next t hing t o do is t o t hink about t est s for t he t ruck calculat ion. At first I t hought it was very sim ple, but t he m ore I t hought about it , I found out it was act ually pret t y com plex. So let 's st art out by adding a sim ple t est . Let t he t est check t hat when no input is given, zero t rucks should be needed. First , I creat e a new proj ect t hat I call Tests. I add a class t hat I call TRuckCalculationTests, and I do t he necessary preparat ions for m aking it a t est fixt ure according t o NUnit , such as set t ing a reference t o nunit.framework, adding a using clause t o NUnit.Framework, and decorat ing t he class wit h [TestFixture] . Then I writ e a first t est , which looks like t his: [Test] public void WillGetZeroAsResultWhenNoInputIsGiven() { TruckCalculation tc = new TruckCalculation(); Assert.AreEqual(0, tc.NeededNumberOfTrucks); }
As a m at t er of fact , when writ ing t hat ext rem ely sim ple t est , I act ually m ade a couple of sm all design decisions. First , I decided t hat I need a class called truckCalculation. Second, I decided t hat t hat class should have a propert y called NeededNumberOfTrucks. Of course, t he t est proj ect won't com pile yet because we haven't writ t en t he code t hat it t est s, so let 's cont inue wit h adding t he " real" proj ect , so t o speak. I add anot her proj ect t hat I call Dynapac.PlanPave.DomainModel . I n t hat proj ect I add a class like t his: public class TruckCalculation {
public int NeededNumberOfTrucks { get {return -1;} } }
What is a bit irksom e is t hat I fake t he ret urn value t o - 1. The reason is t o force a failure for m y t est . Rem em ber, we should always st art wit h an unsuccessful t est , and for t his specific t est j ust ret urning zero would clearly not fail. I t should fail m eaningfully, but I can't com e up wit h a really m eaningful failure and st ill st art out as sim ple ( neit her now nor when I wrot e t his in t he real proj ect ) , so t his will have t o do. Then we go back t o t he Tests proj ect and set a reference t o t he Dynapac.Plan-Pave.DomainModel proj ect . We also add a using clause and build t he whole solut ion. Aft er t hat we execut e t he t est s ( or act ually only t he t est at t his point ) in t he Tests proj ect . Hopefully we get a red bar. Then we go back t o t he propert y code and change it so it ret urns zero inst ead: re- execut e t he t est s and a green bar. I s t here anyt hing t o refact or? Well, I 'm pret t y happy wit h t he code so far. I t 's t he sim plest code I can t hink of right now t hat sat isfies t he t est s ( oragaint he t est ) . We have t aken a sm all st ep in t he right direct ion. We have st art ed working on t he new funct ionalit y for calculat ing num ber of t rucks, and we have a first sim plebut goodlit t le t est . Let 's t ake anot her sm all st ep. Again, we t ake t he st ep by let t ing t est s drive our progress. So we need t o com e up wit h anot her t est . I n t he t est ing area, I need t o show bot h a rounded result and a m ore exact result , at least t o one decim al. The rounded result m ust always be rounded up because it 's hard t o creat e a half t ruck. ( I know what you are t hinking, but it 's not in t he cust om er requirem ent s t o deal wit h a m ix of different - sized t rucks.) That 's a decent and necessary t est , but I don't feel like dealing wit h it now. I want t o t ake a m ore int erest ing st ep, so I add t he rounding t est wit h t he Ignore at t ribut e like t his: [Test, Ignore("Deal with a little later")] public void CannotRoundDecimalTruckDown() { }
I nst ead, I 'd like t o t ake a st ep wit h t he calculat ion. Heck, it can't be so hard t o calculat e t his. I have t o t ake t ransport at ion dist ance int o account , as well as t ransport at ion speed, t ruck capacit y, m illing capacit y, unloading t im e, loading t im e, and probably a couple of ot her t hings. Moving on and sim plifying a bit , let 's say t hat I don't care about t ransport at ion for now, nor t he t im e for loading. I won't even care about ot her fact ors t hat are as yet unknown t o m e. The only t hings I care about now are m illing capacit y, t ruck capacit y and t im e for unloading. So t hings are sim ple enough for t he m om ent . I can writ e a t est like t his: [Test] public void CanCalculateWhenOnlyCapacityIsDealtWith() { TruckCalculation tc = new TruckCalculation(); tc.MillingCapacity = 20; tc.TruckCapacity = 5; tc.UnloadingTime = 30; Assert.AreEqual(2, tc.NeededNumberOfTrucks); }
What I j ust did was t o assum e t he m illing capacit y was 20 t ons/ hour and each t ruck can deal wit h 5 t ons each t im e, which m eans 4 unloadings. I also assum ed t hat unloading t he t ruck t akes 30 m inut es, so each t ruck can only be used t wice in one hour. That should m ean t hat we need 2 t rucks, so I t est for t hat .
N ot e The calculat ion m ight seem a bit st range so far because t oo few fact ors are t aken int o account and I j ust t ried t o get st art ed wit h it . The im port ant t hing here isn't t he calculat ion it self, but t he process of how t o m ove forward wit h TDD, so please don't let t he calculat ion it self dist ract you.
When I t ry t o com pile t he Tests proj ect , it fails because t he new t est expect s t hree new propert ies. Let 's add t hose propert ies t o t he TRuckCalculation class. They could look like t his: //TruckCalculation public int TruckCapacity = 0; public int MillingCapacity = 0; public int UnloadingTime = 0;
Hm m m , t hat wasn't even propert ies, j ust public fields. We'll discuss t his lat er. Now we can build t he solut ion, and hopefully we'll now get a red bar. Yes, expect ed and want ed. We need t o m ake a change t o NeededNumberOfTrucks, t o m ake a real ( or, at least , m ore real) calculat ion. //TruckCalculation public int NeededNumberOfTrucks { get { return MillingCapacity / (TruckCapacity * 60 / UnloadingTime); } }
I now realize t hat t his was perhaps t oo big a leap t o t ake here, but I 'm feeling confident now. Let 's build again and t hen re- execut e t he t est s. Green bar; oops...r e d. How can t hat be? Ah, it wasn't t he last t est t hat was t he problem ; t hat t est runs successfully. I t 's t he old t est t hat now fails. I get an except ion from it . Of course, t he first t est doesn't give any values t o t he truckCapacity and UnloadingTime propert ies, so I get a division by zero. A silly m ist ake t o m ake, but I 'm act ually very happy about t hat red bar. Even t hat t iny lit t le first t est helped m e by finding a bug j ust m inut es ( or even seconds) aft er I creat ed t he bug, and t his ext rem ely short feedback loop is very powerful. I need t o change t he calculat ion a bit m ore:
//TruckCalculation public int NeededNumberOfTrucks { get { if (TruckCapacity != 0 && UnloadingTime != 0) return MillingCapacity / (TruckCapacity * 60 / UnloadingTime); else return 0; } }
So we build, run t he t est s, and get a green bar. Goodanot her st ep in t he right direct ion, secured ( at least t o a cert ain degree) wit h t est s. Any refact orings? Well, I 'm pret t y sure several of you hat e m y usage of t he public fields. I used t o hat e t hem m yself, but I have since changed m y m ind. As long as t he fields are bot h readable and writ able and no int ercept ion is needed when reading or writ ing t he values, t he public fields are at least as good as propert ies. The good t hing is t hat t hey are sim pler; t he bad t hing is if you need t o int ercept when one of t he values is set . I will refact or t hat lat er, if and when necessary, and not before.
N ot e There is a difference bet ween public fields and public propert ies when it com es t o reflect ion, and t hat m ight creat e problem s for you if you choose t o swit ch bet ween t hem . A reviewer point ed out anot her difference t hat I didn't t hink about : t he fact t hat public propert ies can't be used as ref argum ent s, but t hat 's possible wit h public fields. This whole discussion is also language dependent . I n t he case of C# / VB.NET, a public field and public propert y is used t he sam e way by t he consum er, but t hat 's not t he case wit h C+ + and Java, for exam ple.
Anot her t hing I could refact or is t o add a [SetUp] m et hod t o t he t est class t hat inst ant iat es a calculat ion m em ber on t he inst ance level. That 's right ; don't forget about your t est s when you t hink about refact oring. Anyway, I can't say I feel com pelled t o do t hat change eit her, at least not right now. I t would reduce duplicat ion a lit t le bit , but also m ake t he t est s slight ly less clear. I n t he case of t est s, clarit y is oft en a higher priorit y. Yet anot her t hing t hat I 'm not very happy about is t hat t he code for t he calculat ion it self is a bit m essy. I t hink a bet t er solut ion would be t o t ake away what I t hink will be t he least com m on sit uat ion of zero values in a guard. I n t his way, t he ordinary and real calculat ion will be clearer. This change is not ext rem ely im port ant and is m ore a m at t er of personal t ast e, but I t hink it m akes t he code a lit t le m ore readable. Anyway, let 's m ake t he refact oring called Replace Nest ed Condit ional wit h Guard Clauses [ Fowler R] : //TruckCalculation public int NeededNumberOfTrucks {
get { if (TruckCapacity == 0 || UnloadingTime == 0) return 0; return MillingCapacity / (TruckCapacity * 60 / UnloadingTime); } }
N ot e Just like pat t ern nam es, refact oring nam es can be used as a m eans of com m unicat ion am ong developers.
Build t he t est s and t hen run t hem . We st ill see green bars, and t he code is clearer, but we can do bet t er. I t hink it 's a good idea here t o use Consolidat e Condit ional Expression [ Fowler R] like t his: //TruckCalculation public int NeededNumberOfTrucks { get { if (_IsNotCalculatable()) return 0; return MillingCapacity / (TruckCapacity * 60 / UnloadingTime); } }
And while I 'm at it , t he form ula is a bit unclear. I reveal t he purpose bet t er if I change part of it int o a propert y call inst ead by using Ext ract Met hod refact oring [ Fowler R] like t his: public int NeededNumberOfTrucks { get { if (_IsNotCalculatable()) return 0; return MillingCapacity / _SingleTruckCapacityDuringOneHour; } }
That 's fine for now, but it 's t im e t o add anot her t est . However, I t hink t his lit t le int roduct ion t o
writ ing a new class for calculat ing t he needed num ber of t rucks was enough t o show t he flow of TDD. Som e of you m ight dislike t hat you don't get help from int ellisense ( which helps you cut down on t yping by " guessing" what you want t o writ e) when writ ing t est s first . I 'm fond of int ellisense t oo, but in t his case I don't m iss it . Rem em ber, what we are t alking about here is t he int erface, and it could be good t o writ e it t wice t o get an ext ra check. Also not e t hat you should vary your speed depending upon how confident you are wit h t he code you are writ ing at t he m om ent . I f it t urns out t hat you are t oo confident and t oo eager so t hat you get int o hard problem s, you can slow down and writ e sm aller t est s and sm aller chunks t o get back t o m oving forward.
Design Effects During t he dem o I said t hat writ ing t he t est s was very m uch a design act ivit y. Here I have list ed som e of t he effect s I have found when designing wit h TDD inst ead of det ailed up- front design: M or e clie n t con t r ol I used t o t hink t hat as m uch as possible should be hidden from t he out side regarding configurat ion so t hat classes configure t hem selves. To use TDD, you need t o m ake it possible for t he t est s t o set up t he inst ances t he way t hey need t hem . This is act ually a good t hing because t he classes becom e m uch easier t o reuse. ( Of course, wat ch out so you don't open up t he pot ent ial for m ist akes.) M or e in t e r fa ce s/ fa ct or ie s I n order t o m ake it possible ( or at least easier) t o use st ubs or m ock obj ect s, you will find t hat you need t o gat her funct ionalit y via int erfaces so you can pass in t est obj ect s rat her t han t he real obj ect s, which m ight be hard t o set up in t he t est environm ent . I f you com e from a COM background, you will have learned t he hard way t hat working wit h int erfaces has m any ot her m erit s.
N ot e There will be m ore discussion about st ubs and m ocks lat er on in t he chapt er. For now, let 's say t hat st ubs are " st and- ins" t hat are lit t le m ore t han em pt y int erfaces t hat provide canned values creat ed for t he sake of being able t o develop and t est t he consum ers of t he st ubs. Mocks are anot her kind of " st and- ins" for t est purposes t hat can be set up wit h expect at ions of how t hey should be called, and aft erward t hose expect at ions can be verified.
M or e su b- r e su lt s e x pose d I n order t o be able t o m ove a t iny st ep at a t im e, you need t o expose sub- result s so t hat t hey are t est able. ( I f I had cont inued t he dem o, you would have seen m e expose m ore sub- result s for t he t ransport at ion and load t im e, for exam ple.) As long as t he sub- result s are j ust read only, t his is not such a big deal. I t 's com m on t hat you will need t o expose sub- result s in t he user int erface anyway. Just be careful t hat you don't expose t oo m uch of your algorit hm s. Balance
t his carefully against t he ordinary t arget of inform at ion hiding. M or e t o t h e poin t I f I had st art ed wit h det ailed up- front design for t he dem o exam ple, I 'm pret t y sure t hat I would have invent ed a t ruck class holding ont o loads of propert ies for t rucks. Because I j ust focused on what was needed t o m ake t he t est s run, I skipped t he t ruck class com plet ely. All I needed t hat was closely t ruck- relat ed was a t ruck capacit y propert y, and I kept t hat propert y direct ly on t he t ruck calculat ion class. Le ss cou plin g TDD yields less coupling. For inst ance, because UI is hard t o t est wit h aut om at ic t est s, t he UI will m ore or less be forced not t o int erm ingle wit h t he Dom ain Model. The coupling is also great ly reduced t hanks t o t he increased usage of int erfaces. As I have already st at ed, t he design effect s are not t he only good t hings. There are loads of ot her effect s, such as Se con d ve r sion of API When you use TDD, t he API has already been used once when t he t im e com es for your consum er t o use it . Probably, t he consum er will find t hat t he API is bet t er and m ore st able t han it would have been ot herwise. Fu t u r e m a in t e n a n ce Maint enance goes like a dream when you have ext ensive t est suit es. When you need t o m ake changes, t he t est s will t ell you im m ediat ely if it breaks on consum ers. I f you don't have ext ensive t est suit es when you need t o m ake changes, you can go ahead and creat e t est s before m aking t he changes. I t 's not m uch fun, and you probably won't be able t o creat e high- qualit y t est s long aft er t he code was writ t en, but it 's oft en a bet t er approach t han j ust m aking t he changes and crossing your fingers. You t hen have t est s when it 's t im e for t he next change, and t he next . Despit e using TDD, bugs will appear in product ion code. When it happens and you are about t o fix t he bug, t he process is very sim ilar t o t he ordinary TDD one. You st art by writ ing a t est t hat exposes t he bug, and t hen writ e t he code t hat m akes t he t est s ( t he new and all t he old t est s) execut e successfully. Finally, you refact or if necessary. D ocu m e n t a t ion The t est s you writ e are high- qualit y docum ent at ion. This is yet anot her good reason for writ ing t hem in t he first place. Exam ples are really helpful in order t o underst and som et hing, and t he t est s are exam ples bot h of what should work and what shouldn't . Anot her way of t hinking about t he unit t est s is t hat you reveal your assum pt ions, and t hat goes for t he ot her developers, t oo. I m ean, t hey reveal t heir assum pt ions about your class. I f t heir t est s fail, it m ight be because your class isn't behaving as is expect ed, and t his is very valuable inform at ion t o you. A sm a r t e r com pile r I n t he past I have t ried hard t o m ake t he com piler help m e by t elling m e when I do som et hing wrong. Test s are t he next level up from t hat . The t est s are pret t y good at finding out what t he com piler m isses. Anot her way of st at ing it is t hat t he com piler is great at finding synt ax
problem s, and t he t est s will find sem ant ic problem s. I t 's even t he case t hat you probably will value t ype safet y a lit t le less when you are using TDD because t est s will cat ch t ype m ist akes easily. That in it s t urn m eans t hat you can t ake advant age of a m ore dynam ic st yle. I f you follow t he ideas of TDD, you will also find t hat you don't need t o spend nearly as m uch t im e wit h t he debugger as you would ot herwise have t o.
N ot e Even so, t here m ight be sit uat ions where you need t o inspect t he values of variables, and so on. You can use Console.WriteLine() calls, and you can run t he t est s wit hin t he debugger. Personally I t hink it 's a sign of t oo big leaps or t oo few t est s when I get t he urge t o use t hose t echniques.
Re u sa bilit y ch a n ce in ot h e r t e st s The t est s you writ e during developm ent will also com e in handy for t he int egrat ion t est s. I n m y experience, m any product ion t im e problem s are caused because of differences in t he product ion environm ent com pared t o what is expect ed. I f you have an ext ensive set of t est s, you can use t hem for checking t he deploym ent of t he finished applicat ion as well. You pay once, but reap t he benefit s m any t im es over. To sum m arize it all, t est - friendly design ( or t est abilit y) is a very im port ant design goal nowadays. I t 's perhaps t he m ain fact or when you m ake design choices.
Problems I have sounded like a salesm an for a while. However, t here are problem s t oo. Let 's have a look at t he following: UI I t is hard t o use TDD for t he UI . TDD fit s easier and bet t er wit h t he Dom ain Model and t he core logic, alt hough t his isn't necessarily a bad t hing. I t helps you be st rict in split t ing t he UI from t he Dom ain Model, which is a basic design best pract ice ( so it should be done anyway, but TDD st im ulat es it ) . Ensure you writ e very t hin UI and fact or out logic from t he form s. Pe r sist e n t da t a Dat abases do cause a t ricky problem wit h TDD because once you have writ t en t o t he dat abase in one t est , t he dat abase is in anot her st at e when it 's t im e for t he next t est , and t his causes t rouble wit h isolat ion. You could writ e your code t o set up t he dat abase t o a well- known st at e bet ween each t est , but t hat adds t o t he t est er's burden and t he t est s will run m uch m ore slowly. You could also writ e t est s so t hat t hey aren't affect ed by t he changed st at e, which m ight reduce t he power of t he t est s a bit . Yet anot her solut ion is t o use st ubs or m ock obj ect s t o sim ulat e t he
dat abase apart from during t he int egrat ion t est s when a backup of t he dat abase is rest ored before t he t est s. Yet anot her way is t o use t ransact ions purely for t he sake of t est ing. Of course, if your t est s focus on t he t ransact ional sem ant ics, t his isn't a useful solut ion.
N ot e We will discuss dat abase bound t est ing m ore in Chapt er 6, " Preparing for I nfrast ruct ure."
Fa lse se n se of se cu r it y Just because you get a green bar doesn't m ean t hat you have zero bugsit j ust m eans t hat your t est s haven't det ect ed any bugs. Make sure t hat TDD doesn't lead you int o a false sense of securit y. Rem em ber t hat a green t est doesn't prove t he absence of bugs, only t hat t hat part icular t est execut ed wit hout det ect ing a problem . On t he ot her hand, j ust because your t est s aren't perfect doesn't m ean t hat you shouldn't use t hem . They will st ill help you quit e a lot wit h im proving t he qualit y! As m y friend Mat s Helander once said, t he value isn't in t he green, but in t he red! Losin g t h e ove r vie w TDD can be seen as a bot t om - up approach, and you m ust be aware t hat you m ight lose t he overview from t im e t o t im e. I t 's definit ely a very good idea t o visualize your code from t im e t o t im e as UML, for exam ple, so you get a bird's eye perspect ive. You will m ost cert ainly find several refact orings t hat should be applied t o prepare your code for t he fut ure. M or e code t o m a in t a in The m aint enance aspect is a double- edged sword. The t est s are great for doing m aint enance work, but you also have m ore code t o m aint ain. I j ust said t hat TDD is hard t o apply for t he UI and dat abase, and t hat 's j ust one m ore reason why t he Dom ain Model pat t ern shines because you can t ypically run m ore m eaningful t est s wit hout t ouching t he dat abase and t he UI com pared t o when ot her logical st ruct ures have been used! That said, we will discuss bot h dat abase t est ing and UI t est ing in lat er chapt ers. Anyway, all in all I t hink TDD is a great t ool for t he t oolbox!
The Next Phase? That was an int roduct ion t o t he basic concept s, but we have only scrat ched t he surface a lit t le bit . When you st art applying TDD, it will probably work sm oot hly aft er you pass t hose first st art - up problem s. Then aft er a while you will probably find t hat set t ing up som e t est s get s overly com plex and t oo dependent on unint erest ing part sunint erest ing for t he t est it self at least . That 's a good sign t hat you have reached t he next phase of TDD. My friend Claudio Perrone will t alk m ore about t hat in t he next
sect ion called m ocks and st ubs.
Mocks and Stubs I said t hat we will t alk about dat abase t est ing lat er in t he book. I t 's act ually already t im e for t he first incarnat ion of t hat . Here Claudio Perrone will discuss how you apply t he t echnique of st ubs and m ocks in your t est ing. I n t he previous sect ion, I t alked about what is called st at e- based t est ing. Here Claudio will focus on int eract ion- based t est ing. To not run in advance, Claudio will use a classic approach for his dat abase- relat ed exam ples before we really dive int o using t he Dom ain Model pat t ern in t he next chapt er. Over t o Claudio.
A Typical Unit Test By Claudio Perrone A com m on approach for t est ing t he behavior of an obj ect is t o set it up wit h relevant cont ext inform at ion, call one of it s m et hods, and writ e a few assert ions t o check t he ret urn value or t o verify t hat t he m et hod changed t he st at e of t he environm ent as expect ed. The following exam ple, which t est s t he SaveUser() m et hod of t he UserBC business com ponent , illust rat es t his approach: [Test, Rollback] //Automatic rollback //(using Services w/o components) public void TestUserBCCanSaveUser () { // Setting up context information (preconditions) IUserInfo user = new UserInfo( "Claudio", "Perrone", "MyUniqueLogin", "MyPassword"); // I'm not testing yet, just clarifying initial state Assert.IsTrue(user.IsNew); Assert.AreEqual(NEW_OBJECT_ID, user.ID); Assert.IsFalse( IsUserInsertedInDataBase( "Claudio", "Perrone", "MyUniqueLogin", "MyPassword")); // Preconditions are ok - now exercising method to test UserBC bcUser = new UserBC(); bcUser.SaveUser(user); // Verifying post-conditions (state changed as expected) Assert.IsFalse(user.IsNew); Assert.IsTrue(user.ID != NEW_OBJECT_ID); Assert.IsTrue( IsUserInsertedInDataBase( "Claudio", "Perrone", "MyUniqueLogin", "MyPassword")); }
N ot e The Rollback at t ribut e is not provided wit h current NUnit yet ( alt hough it is in t heir roadm ap) . I used a m odified version writ t en by Roy Osherove t o be found here: ht t p: / / weblogs.asp.net / rosherove/ archive/ 2004/ 07/ 12/ 180189.aspx.
Because t he UserBC business com ponent t akes a business ent it y and saves it t o t he dat abase, t he t est checks it s behavior by creat ing t he ent it y, passing it as a param et er t o t he SaveUser() m et hod, and verifying t hat t he ent it y is persist ed t o t he dat a st ore.
Declaration of Independence A pot ent ial problem wit h t his t est ing st yle is t hat obj ect s very rarely operat e in isolat ion. Ot her obj ect s t hat our obj ect under t est depends on oft en carry out part of t he work. To illust rat e t his issue wit h our exam ple, we could im plem ent t he UserBC class as follows ( for convenience, all except ion- handling code is om it t ed) : public class UserBC { public void SaveUser(IUserInfo user) { user.Validate(); UserDao daoUser = new UserDao(); if (user.IsNew) daoUser.Insert(user); else daoUser.Update(user); } }
I n t his case, UserBC delegat es t he user validat ion t o t he UserInfo business ent it y. I f one or m ore business rules are broken, UserInfo will t hrow a cust om except ion cont aining a collect ion of validat ion errors. UserBC also delegat es t he persist ence of t he business ent it y t o t he UserDao dat a access obj ect t hat has responsibilit y for abst ract ing all dat abase im plem ent at ion det ails. As IUserInfo is an explicit param et er of t he SaveUser() m et hod, we can argue t hat UserBC explicit ly declares a dependency on t he IUserInfo int erface ( im plem ent ed by t he UserInfo class) . However, t he dependency on UserDao is som ewhat hidden inside t he im plem ent at ion of t he UserBC class. To com plicat e m at t ers furt her, dependencies are t ransit ive. I f UserBC depends on UserInfo and, for exam ple, UserInfo needs a set of BusinessRule obj ect s t o validat e it s cont ent , UserBC depends on BusinessRule. A bug in a BusinessRule obj ect could suddenly break our t est and several ot hers at t he sam e t im e. I ndeed, t he com plex logic handled by a Dom ain Model is oft en im plem ent ed t hrough a chain of obj ect s t hat forward part of t he behavior t o ot her collaborat ing obj ect s unt il t he required result is creat ed. Consequent ly, unit t est s t hat aim at verifying t he behavior of an obj ect wit h m any
dependencies m ight fail if one of t he obj ect s in t he chain has bugs. As a result , it is som et im es difficult t o ident ify t he cause of an error, as t he t est s are essent ially sm all- scale int egrat ion t est s, rat her t han " pure" unit t est s.
Working with Difficult Team Members There is anot her problem relat ed t o collaborat ors t hat is very com m on in t his world of dist ribut ed applicat ions. How can we t est an obj ect 's behavior when t he m et hod we t ry t o exercise depends on com ponent s or condit ions t hat are difficult t o recreat e? For exam ple, we m ight have com ponent s t hat Have not yet been im plem ent ed Are difficult t o set up or t est ( for exam ple, user int erface com ponent s or m essaging channels) Are t oo slow ( such as dat a access layer com ponent s, service agent s, or dist ribut ed com ponent s) Cont ain behavior t hat is difficult t o reproduce ( such as int erm it t ent net work connect ivit y or concurrency issues)
Replacing Collaborators with Testing Stubs A viable solut ion t o t he previous problem s is t o replace som e or all of t he collaborat ors wit h t est ing st ubs. A t est ing st ub is a sim ulat ion of a real obj ect used for t est ing purposes. I t provides a m echanism t o set up expect ed values t o supply t o t he code being t est ed. Let 's go back t o our exam ple. I n t his case, we have t wo close collaborat ors in t he SaveUser() m et hod UserInfo and UserDao. UserInfo is easy t o subst it ut e, as our t est sim ply needs t o provide an obj ect t hat im plem ent s t he IUserInfo int erface and does not t hrow except ions when it s Validate() m et hod is called ( unless, of course, we want t o t est t he behavior of t he SaveUser() m et hod when a validat ion except ion occurs) . UserDao is m uch m ore difficult t o replace because t he const ruct ion of t he obj ect is em bedded inside t he UserBC class. We really want t o subst it ut e t his collaborat or, however, because it s access t o t he
dat abase will slow down t he execut ion of our t est s. Addit ionally, checking values in t he dat abase is t im e consum ing and possibly of value only when t est ing UserDao in isolat ion or wit hin an int egrat ion t est . One viable solut ion is t o ext ract t he int erface from UserDao and add a const ruct or t o t he UserBC class t hat set s an explicit dependency on IUserDao . The required code is very sim ple, wit h very low probabilit y of int roducing bugs. I n som e cases, you m ay even consider writ ing it in addit ion t o t he exist ing const ruct or( s) for t est ing purposes only. public class UserBC { private IUserDao _daoUser; // Default constructor public UserBC() { _daoUser = new UserDao(); } // Constructor used by testing code public UserBC(IUserDao daoUser)
{ _daoUser = daoUser; } public void SaveUser(IUserInfo user) { user.Validate(); if (user.IsNew) _daoUser.Insert(user); else _daoUser.Update(user); } }
Now we can easily creat e a couple of st ubs t hat im plem ent IUserInfo and IUserDao . Their im plem ent at ion is t rivial, so let 's exam ine UserDaoStub only. public class UserDaoStub : IUserDao { private IUserInfo _userResult = null; // Note: Test will need to set the expected value // to be returned when Insert is called public IUserInfo UserResult { get { return _userResult;} set { _userResult = value;} } public void Insert(IUserInfo user) { // Note: Before calling this method our test will need to // set up the UserResult property with the expected values user.ID = UserResult.ID; user.Name = UserResult.Name; // etc } . . . }
There are a few im port ant aspect s t o consider at t his point . First , our init ial t est was set t ing a couple of expect at ions about t he st at e of t he IUserInfo obj ect as a result of calling t he SaveUser() m et hod. I f we m odify our t est so t hat it uses a UserDaoStub obj ect , we will also need t o set up t he expect ed result by set t ing t he UserResult propert y before t he Insert() m et hod is execut ed. A second aspect t o consider is t hat t he dependency on t he dat abase is now com plet ely rem oved and our init ial t est is now m uch fast er. Because SaveUser() delegat es m ost of it s behavior, however, such a t est provides virt ually no value. Act ually, on second t hought , I would also add t hat we are com m it t ing t he deadly ( but unfort unat ely com m on) sin of put t ing assert ions on values ret urned from our st ubs. So we are effect ively t est ing
our st ubs rat her t han UserBC! On t he ot her hand, it 's a different st ory if we want t o know how our UserBC class react s when t he dat a access layer t hrows an except ion such as DalUnique-ConstraintExceptiona sit uat ion t hat could occur if t he user login already exist s on t he dat abase. The Insert() m et hod in t he st ub class now becom es //UserDaoStub public void Insert(IUserInfo user) { if (ThrowDalUniqueConstraintExceptionOnInsert) throw new DalUniqueConstraintException(); user.ID = UserResult.ID; user.Name = UserResult.Name; // etc }
Assum e we'd like t he UserBC t o cat ch t he DalUniqueConstraintException and t hrow a proper business except ion t o m aint ain t he abst ract ion of t he layers. The following t est illust rat es t his scenario: [Test] // Note no database is needed anymore! [ExpectedException( typeof(BusinessException), "The provided login already exists.")] public void TestUserBCSaveUserThrowsBusinessException() { // Setting stubs to remove all dependencies IUserInfo user = new UserInfoStub ( "Claudio", "Perrone", "Login", "MyPassword"); UserDaoStub daoUser = new UserDaoStub(); daoUser.ThrowDalUniqueConstraintExceptionOnInsert = true; // Executing test - it should throw a business exception UserBC bcUser = new UserBC(daoUser); bcUser.SaveUser(user); }
As you m ight expect , t he t est is very fast , doesn't require access t o t he dat abase or ot her collaborat ors, and allows us t o quickly verify t he funct ionalit y of t he class under t est in a condit ion t hat is pot ent ially hard t o recreat e. Sim ulat ing ot her condit ions becom es a very sim ple exercise. This brings us t o an im port ant lesson about st ubs. Using t est ing st ubs allow us t o isolat e t he code under t est and t o observe how it react s t o t he ext ernal condit ions sim ulat ed by t he faked collaborat ors.
Replacing Collaborators with Mock Objects A not able variat ion t o st ubs is t he concept of m ock obj ect . A m ock obj ect is a sim ulat ion of a real obj ect . I t replaces a collaborat or and provides expect ed values t o t he code under t est . I n addit ion, it supplies a m echanism t o set up expect at ions about how it should be used and can provide som e self-
validat ion based on t hose expect at ions. To illust rat e what t his m eans, let 's creat e a t est t hat focuses on t he int eract ion of UserBC wit h t he collaborat ing obj ect s. This t im e we will use a popular .NET m ock obj ect s fram ework called NMock [ NMock] . A nice feat ure of t his open source fram ework is t hat it uses reflect ion t o generat e m ocks dynam ically from exist ing classes or int erfaces at run t im e. [Test] public void TestUserBCSaveUserInteractsWell() { // (1 - Setup) Create mocks dynamically based on interface DynamicMock mockUser = new NMock.DynamicMock(typeof(IUserInfo)); DynamicMock mockUserDao = new DynamicMock(typeof(IUserDao)); // Set up canned values (same as stubs) mockUser.SetupResult("FirstName", "Claudio", typeof(string)); . . . mockUser.SetupResult("ID", NEW_OBJECT_ID, typeof(int)); mockUser.SetupResult("IsNew", true, typeof(bool)); // Generate mock instances (need to cast) IUserInfo user = (IUserInfo) mockUser.MockInstance; IUserDao daoUser = (IUserDao) mockUserDao.MockInstance; // (2 - Expectations) How we expect UserBC to deal with // mocks mockUser.Expect("Validate"); mockUserDao.Expect("Insert", user); mockUserDao.ExpectNoCall("Update", typeof(IUserInfo)); // (3 - Execute) Executing method under test UserBC bcUser = new UserBC(daoUser); // Unexpected calls on the mocks will fail here // (e.g. calling Validate twice) bcUser.SaveUser(user); // (4 - Verify) Checks that all expectations have been met mockUser.Verify(); mockUserDao.Verify(); // Note: No need for assertions }
N ot e NMock eit her produces a class t hat im plem ent s an int erface or generat es a subclass of a real class. I n bot h cases, we t hen use polym orphism t o replace t he real class wit h an inst ance of t he generat ed class. Alt hough really powerful, t his approach present s som e not able lim it at ions. For exam ple, it is not possible t o creat e m ocks of sealed classes, and m ocked m et hods m ust be m arked as virtual. An int erest ing alt ernat ive t o NMock is a com m ercial fram ework called POCMock [ POCMock]
from Pret t y Obj ect s. I n t his case, m ocks are creat ed by replacing collaborat ors st at ically.
As you can see, t here is no need for assert ions in our t est because t hey are locat ed inside t he m ock obj ect s and are designed t o ensure t hat t he m ocks are called as expect ed by t he code under t est . For exam ple, calling Validate() t wice would im m ediat ely t hrow an except ion even before t he call t o Verify() is m ade. This exam ple leads us t o t he following observat ion: As m ock obj ect s verify whet her t hey are used correct ly, t hey allow us t o obt ain a finer underst anding of how t he obj ect under t est int eract s wit h t he collaborat ing obj ect s.
Design Implications When I first learned about m ock obj ect s, I t hought t hat it was part icularly t ricky t o com e up wit h an easy m echanism t o replace m y collaborat ors. The fundam ent al problem was t hat m y code was coupled wit h t he concret e im plem ent at ion of t hose collaborat ors rat her t han t heir int erfaces. The " aha! " m om ent cam e when I discovered t wo key m echanism s called Dependency I nj ect ion and Service Locat or . The first principle, Dependency I nj ect ion, suggest s t hat a class explicit ly declares t he int erfaces of it s collaborat ors ( for exam ple, in t he const ruct or or as param et ers in a m et hod) but leaves t he responsibilit y for t he creat ion of t heir concret e im plem ent at ion t o t he cont ainer. Because t he class is not in cont rol of t he creat ion of it s collaborat ors anym ore, t his principle is also known as I nversion of Cont r ol. The second principle, Service Locat or, m eans t hat a class int ernally locat es it s concret e collaborat ors t hrough t he dependency on anot her obj ect ( t he locat or) . A sim ple exam ple of a locat or could be a fact ory obj ect t hat uses a configurat ion file t o load t he required collaborat ors dynam ically.
N ot e There will be m uch m ore coverage about Dependency I nj ect ion, I nversion of Cont rol, and Service Locat or in Chapt er 10, " Design Techniques t o Em brace."
Consequences Mock obj ect s and st ubs perm it you t o furt her isolat e t he code t hat needs t o be t est ed. This is an advant age, but can also be a lim it at ion, as t est s can occasionally hide int egrat ion problem s. While t est s t end t o be quicker, t hey are oft en coupled t oo closely wit h t he syst em under t est . Consequent ly, t hey t end t o becom e obsolet e as soon as a bet t er im plem ent at ion for t he syst em under t est is found. Refact oring act ivit ies aim ed at int roducing m ocks t end t o decouple obj ect s from a part icular im plem ent at ion of t heir dependencies. Alt hough it is generally achievable t o creat e m ocks from classes cont aining virt ual m et hods, it is usually recom m ended t o use int erfaces whenever possible. As a result , it is not rare t o observe t hat syst em s designed t o be t est ed using m ock obj ect s cont ain a significant num ber of int erfaces int roduced for t est ing purposes only.
Further Information For a m ore in- dept h discussion of t he differences bet ween m ock obj ect s and st ubs ( and st at e- based versus int eract ion- based t est ing) , see Mart in Fowler's " Mocks Aren't St ubs" [ Fowler Mocks Aren't St ubs] . Thanks Claudio! We are st rengt hened wit h yet anot her t ool; can we resist get t ing yet one m ore? Next up is refact oring.
Refactoring I m ent ioned refact oring previously as t he t hird st ep in t he general process of TDD, and I t hink I should briefly explain t he t erm a bit m ore. Refact oring is about m aking sm all, well- known changes st ep- by- st ep in order t o im prove t he design of exist ing code. That is, you m ake changes t o im prove t he m aint ainabilit y of t he code wit hout changing it s observed behavior. Anot her way t o say it is t o change how , not what . The t erm " refact oring" has becom e som et hing of a buzzword; it 's m uch overused and also m isused. For inst ance, inst ead of saying t hat we are going t o m ake loads of significant and ground- breaking changes t o our code base, we say t hat we will refact or it . That 's not , however, t he cent ral m eaning of t he t erm . We t alked quit e a lot about what refact oring is and why you should use it in Chapt er 1. I n t his sect ion about refact oring, we'll t ake a look at how it 's used. We will st art wit h som e basic refact orings for rout ine cleaning. Then we'll t alk briefly about how product ivit y could gain from new t ooling support . Finally, we'll refact or t o, t oward, or from pat t erns for prevent ing t he occurrence of m aint ainabilit y issues.
Let's Clean Some Smelly Code We did discuss som e refact orings in t he previous TDD exam ple, but I t hink it 's t im e t o have a look at anot her refact oring exam ple. Let 's st art wit h t he following piece of code, a Person class: public class { public public public
Person string Name = string.Empty; DateTime BirthDate = DateTime.MinValue; IList Children = new ArrayList();
public int HowOld() { if (BirthDate != DateTime.MinValue && BirthDate < DateTime.Now) { int years = DateTime.Now.Year BirthDate.Year; if (DateTime.Now.DayOfYear < BirthDate.DayOfYear) years--; return years; } else return 0; } }
Smelly Code I have t alked about sm elly code m any t im es already by now, and code sm ells will be t he focus of our refact oring effort s for t he com ing pages. Therefore, I 'd like t o point out t hat Mart in Fowler and Kent Beck present s a list of code sm ells in t he Refact or ing book [ Fowler R] . A t ypical exam ple, and also t he first one, is t he code sm ell called Duplicat ed Code.
Routine Cleanings The class we j ust saw, Person, is a nice lit t le class t hat probably solves t he problem at hand. Deducing from t he nat ure of t he class, t he requirem ent was t o t rack nam es and birt hdat es of cert ain persons, who t heir children are and how old t hese people are. Again, let 's assum e t hat t he requirem ent s are fulfilled. That 's a good t hing, but we have st art ed t o add t o our t echnical debt because t he code is a bit m essy, and t hat 's where refact oring com es in. We'll clean up t he code wit h a couple of exam ples which I have called " Rout ine Cleanings," each of which is based on Fowler's work [ Fowler R] .
Routine Cleaning Example One: Encapsulate Field The t arget of t he first exam ple is t he Name- field. I t 's current ly a public st ring and t hat m ight be t oo m uch for som e t o t ake. Let 's t ransform it from t his: public string Name = string.Empty;
int o som et hing like t his: //Person private string _name = string.Empty; public string Name { get {return _name;} } public Person(string name) { _name = name; }
I said t hat t he requirem ent s were fulfilled before, didn't I ? Well, t hey were, kind of; it was possible t o set t he Name and read it . However, t he requirem ent s could be fulfilled even m ore successfully. The read- only aspect of t he Name field wasn't addressed before, but it is now. Are we done, or could we do bet t er? Well, I t hink it could be even bet t er t o use a public readonly
field inst ead, like t his: //Person public readonly string Name; public Person(string name) { Name = name; }
I t hink t hat was even clearer, but unfort unat ely, t here are drawbacks as well. The one I 'm t hinking about is t hat it 's not possible t o set t he Name via reflect ion, which is im port ant for m any t ools, such as m any Obj ect Relat ional ( O/ R) Mappers. I n t his case, I need t he opt ion of set t ing t he Name via reflect ion, so I decide t o go for t he get - based version inst ead. That rem inds m e...does all m y consum er code st ill run? Aft er all, t he observable behavior of t he code has act ually changed slight ly because I require t he Name t o get it s value via t he const ruct or. I get com piler errors, so t hat 's sim ple t o sort out ; but are we done? Nope, we aren't done. Assum e t he following snippet : //Some consumer public void SetNameByReflection() { Person p = new Person(); FieldInfo f = typeof(Person).GetField("Name", BindingFlags.Instance | BindingFlags.Public); f.SetValue(p, "Jimmy"); ...
That snippet set s t he Name field via reflect ion ( you m ight wonder why, but t hat 's not im port ant here) . This won't work any longer, but t he com piler won't det ect t he problem . Hopefully we have writ t en aut om at ic t est s t hat will det ect t he problem . So t he lesson learned is t hat refact oring requires aut om at ic t est s! As a m at t er of fact , refact oring and TDD are sym biot ic. As I said at t he st art of t his chapt er, t he TDD m ant ra is Red- Green- Refact or, RedGreen- Refact or.... ( Well, refact oring doesn't require TDD; it requires aut om at ic t est s. On t he ot her hand, using TDD is a great way of get t ing t hose aut om at ic t est s t o be writ t en at all.)
Routine Cleaning Example Two: Encapsulate Collection The second problem I 'd like t o address is t he public list of children, which looked like t his: public IList Children = new ArrayList();
This solut ion m eans t hat t he consum er can add t o and delet e from t he list wit hout t he parent knowing about it . ( To be fair, t hat 's not a violat ion t o any requirem ent s here, but I st ill dislike it .) I t 's also possible t o add strings and ints inst ead of Person- inst ances. I t 's even t he case t hat t he
consum er can swap t he whole list . A t ypical solut ion is t o creat e a t ype safe Children list class, wit h an Add() m et hod t hat only accept s Person- inst ances. That addresses t he problem of lack of t ype safet y. I t could also address t he problem of let t ing t he parent know about t he adds and delet es, but it won't address t he problem of swapping t he whole list . That can be solved wit h Encapsulat e Field refact oring for t he list it self. The problem wit h t his solut ion is t hat you have t o writ e som e dum m y code t hat you don't really want t o have for get t ing t hat t ype safe list . I t 's cert ainly doable, but it 's not t he st yle I current ly prefer. I f we t arget a plat form wit h generics ( such as .NET 2.0) , we can avoid t he lack of t ype safet y in a nicer way. That said, wit h or wit hout generics, I prefer t he st yle of Encapsulat e Collect ion refact oring. First , you use Encapsulat e Field refact oring so t hat you get t he following code: //Person private IList _children = new ArrayList(); public IList Children { get {return _children;} }
At least t he consum er can't swap t he whole list any longer, but t he consum er can st ill add what ever he want s t o t he list and delet e it em s as well. Therefore, t he next st ep is t o hand out a wrapped list t o t he consum er like t his: get {return ArrayList.ReadOnly(_children);}
Finally, we need t o give t he consum er a way of adding elem ent s, so I add an AddChild() m et hod t o t he Person class like t his: //Person public void AddChild(Person child) { _children.Add(child); }
Then we have t he t ype safet y as well, and t he parent knows about when new inst ances are added so it can int ercept t hat act ion ( if it 's needed, whicht o be fairit act ually isn't in t his part icular exam ple) . Unfort unat ely, t his has changed t he visible behavior of t he Person class, and t hat has t o be weighed in as a fact or as t o whet her you can go ahead or not . I n t his case, m ost of t he needed consum er changes aren't found by t he com piler. Luckily, we have som e aut om at ic t est s in place, st anding in sort of as a second com piler layer, det ect ing t he problem . The t est t hat looked like t his helped out : [Test] public void CanCalculateNumberOfKids() { Person p = new Person("Stig"); p.Children.Add(new Person("Ulla")); p.Children.Add(new Person("Inga")); Assert.AreEqual(2, p.Children.Count); }
N ot e To be very clear, as you saw here, t he t est didn't point out t he real consum er code t hat needed t o be changed. I t was j ust som e t est code of t he Person- class. Test s of t he consum er are needed t o point out necessary changes of t he consum er code it self.
The p.Children.Add() lines are easily changed so t hat I get t his inst ead: [Test] public void CanCalculateNumberOfKids() { Person p = new Person("Stig"); p.AddChild(new Person("Ulla")); p.AddChild(new Person("Inga")); Assert.AreEqual(2, p.Children.Count); }
I t 's kind of unfort unat e t hat t he IList has an Add() m et hod even t hough it has been wrapped as a read- only list , but t est s easily cat ch t he problem . To solve t hat , I can change Children t o be an ICollection inst ead. I s t here any m ore t o do? We need t o do som et hing about t he HowOld() m et hod. I t 's not overly clear, is it ?
Routine Cleaning Example Three: Extract Method Perhaps you have anot her, m uch bet t er solut ion t o t he problem at hand? Befor e t hinking about a bet t er solut ion, I 'd like t o sim plify t he code, m aking it easier t o read and underst and. Refact oring is in it self act ually a way of t rying t o underst and t he code. That 's a nice side effect , and t he bet t er we underst and it , t he easier we can find sim plificat ions and bet t er solut ions. This is what we have: //Person public int HowOld() { if (BirthDate != DateTime.MinValue && BirthDate < DateTime.Now); { int years = DateTime.Now.Year - BirthDate.Year; if (DateTime.Now.DayOfYear < BirthDate.DayOfYear) years--; return years; } else return 0
}
First , t here are t wo different sit uat ions t hat will ret urn 0. That 's not good. I prefer t o t hrow an except ion in t he incorrect case. Next , I 'd like t o t ake out t hat non- int uit ive logical expression by using t he Ext ract Met hod refact oring t o get an explaining m et hod nam e inst ead. So we go from if (BirthDate != DateTime.MinValue && BirthDate < DateTime.Now) to if (_CorrectBirthDate())
Alm ost no m at t er what t he logical expression was, it s int ent ion in t he cont ext of t he HowOld() m et hod is now m uch clearer. Anot her st yle now com es t o m ind, t hat of Guard clauses, which m eans t hat I should t ake away t he unusual, alm ost never happens sit uat ion first . I n t his way, I am let t ing go of t he else clause, and t he code now reads like t his: if (!_CorrectBirthDate()) throw new ArgumentException("Incorrect birthdate!");
Hm m m ... Perhaps it 's even bet t er t o change it t o if (_IncorrectBirthDate()) throw new ArgumentException("Incorrect birthdate!");
I t hink t his is slight ly clearer and m ore fluent . What 's left is t he calculat ion it self when we have t he unusual sit uat ion t aken care of. I t looks like t his: int years = DateTime.Now.Year - BirthDate.Year; if (DateTime.Now.DayOfYear < BirthDate.DayOfYear) years--; return years;
Yet again I use t he Ext ract Met hod refact oring, m oving t he calculat ion t o anot her privat e m et hod called _NumberOfYears(). The com plet e HowOld() m et hod t hen looks like t his: //Person public int HowOld() { if (_IncorrectBirthDate()) throw new ArgumentException("Incorrect birthdate!"); return _NumberOfYears(); }
Sure, we could find m ore t o do ( for exam ple in t he newly creat ed privat e m et hod _NumberOfYears() or fact oring out som e logic from t he class it self) , but you get t he m essage by now. I t 's im port ant t o st ress t hat we should re- execut e t he t est s aft er each st ep t o confirm t hat we didn't int roduce any errors, according t o t he t est s at least . What we did, t hough, was t im e- consum ing and error prone. I f you use lot s of discipline, it is possible t o deal wit h t he error proneness, but it 's st ill t im e- consum ing. Having t he concept s in place, but finding out t hat t hey are t im e- consum ing t o apply is a perfect sit uat ion for using t he support of a t ool, if you ask m e.
Cleaning Tool There are several different refact oring t ools around. I f we focus on .NET, we have, for exam ple, ReSharper [ ReSharper] and Refact or! [ Refact or! ] , which are add- ins t o Visual St udio .NET. I n t he 2005 edit ion of Visual St udio .NET, t here is also built - in refact oring support in t he I DE. Those refact oring t ools m ake t he changes sim ilar t o what we j ust m ade in order t o help us becom e m ore product ive. The value of t his should cert ainly not be underest im at ed. As a m at t er of fact , when you get used t o a refact oring t ool, t here's no way you will want t o give it up! The t ools not only help you m ake t he changes you want , t hey can also t ell you about when and what changes need t o be m ade. I n t his way, t he t ools free you t o som e sm all degree from som e of t he discipline of having t o t hink about refact oring by providing a visual rem inder.
N ot e Do you rem em ber t hat I said t hat a failing t est is red and a t est t hat execut es successfully is green, but t hat a need for refact oring doesn't have a color? As a m at t er of fact , t he refact oring t ool ReSharper uses orange t o t ell you t hat refact oring is needed ( according t o ReSharper, t hat is) . So before st art ing a new it erat ion, you should also see t o it t hat you change t he orange int o green by cleaning up t he code before m oving on. Of course, t his won't m ake it possible for you t o st op t hinking about what code is sm elly yourself; it 's j ust a help.
When you see a developer in t he flow wit h a good refact oring t ool in his hands, t he speed at which he m oves is j ust am azing, and wit hout creat ing bad code. I t 's rat her t he opposit e regarding t he code qualit y! So far we have m ost ly dealt wit h sim ple st uff. There's not hing wrong wit h t hat , of course, but from t im e t o t im e you j ust aren't happy wit h t he code even aft er cleaning up t he sm all t hings. I t m ight also be t hat you have a sit uat ion where t here is a st eady st ream of new problem s t hat needs t o be cleaned up.
Prevent Growing Problems Perhaps you t hink t hat using pat t erns is t he nat ural solut ion t o t hat problem . The problem wit h pat t erns is t hat t hey are t radit ionally used for up- front design, which oft en m eans t oo m uch
guesswork ( whereas refact oring, as we have discussed, is used lat er on during developm ent ) . Should you choose pat t erns o r refact oring? Before discussing t hat any furt her, I t hink a short exam ple of anot her pat t ern is in order. I 'd like t o discuss t he Tem plat e Met hod pat t ern [ GoF Design Pat t erns] . The idea is t hat you define t he overall algorit hm , or t em plat e m et hod, in a base class like t his: public abstract class TotalBase { public void TotalAlgorithm() { _DoSomeStuff(); VariationPoint(); _DoSomeMoreStuff(); } protected abstract void VariationPoint(); }
Now t he subclasses can inherit from TotalBase and provide a cust om im plem ent at ion of t he VariationPoint() m et hod. I t could look like t his: public class TotalSub : TotalBase { protected override void VariationPoint() { Console.WriteLine("Hello world!"); } }
The consum er code isn't aware of how t he algorit hm is creat ed. I t doesn't even know about t he base class, only t he subclass. The consum er code could look like t his: //Some consumer TotalSub t = new TotalSub(); t.TotalAlgorithm();
Not e t hat you decide in t he base class if t he hook should be opt ional or m andat ory. I n t he previous exam ple, I used a m andat ory one because I used abstract for t he VariationPoint() m et hod. Wit h virtual, it would have been opt ional inst ead, such as when you want t o m ake it opt ional for subclasses t o define a piece of t he t ot al t em plat e m et hod. Let 's say we decide up front t o use Tem plat e Met hod, which t hen gives us t he problem of deciding where we should m ake variat ions possible or forced. That 's a very t ough decision. Nevert heless, pat t erns are ext rem ely useful, and we don't h av e t o choose; we can use bot h pat t erns and refact oring. As a m at t er of fact , I t hink t hat 's t he way t o go. Refact oring t oward, t o, or from pat t erns! This is what Kerievsky t alks about in his book Refact oring t o Pat t erns [ Kerievsky R2P] . Let 's see t his in act ion wit h a fourt h exam ple, which will connect t o t he pat t ern we j ust t alked about .
Prevent Growing Problems Example One: Form Template Method Let 's assum e t hat we have writ t en a bat ch m onit or in which we can hook in different program s. For a program t o be a bat ch program , it has t o adj ust t o som e rules, and t hose are basically t o t ell t he bat ch m onit or ( by som e cust om logging rout ines) when t he program st art s and finishes. Aft er a while, we will probably have writ t en a couple of bat ch program s, and we will also have decided t o reuse a base class for each bat ch program so t hat we don't have t o writ e t he log code over and over again. An exam ple of t he Execute() m et hod in a subclass m ight look like t his: //A subclass to the batchprogram baseclass public void Execute() { base.LogStart(); _DoThis(); _DoThat(); base.LogEnd(); }
What is sm elly here? As you see, t he subclass is responsible for calling up t o t he base class at cert ain point s in t he execut ion of t he Execute() m et hod. I t hink t hat 's t oo loose a cont ract for t he subclass; t he developer of t he subclass has t o rem em ber t o add t hose calls and t o do it at t he right places. Anot her t hing t hat sm ells here ( which is m ore obvious in a m ore realist ic exam ple where t here are m any log spot s, such as aft er int roduct ion, aft er first phase, aft er second phase and so on) is t hat wit hin t he core m et hod, t here's a lot of infrast ruct ure code, t hat is, t he log calls. Because of t hat , t he calls t o t he int erest ing m et hods, such as _DoThis() and _DoThat(), aren't as obvious as t hey should be. As I see it , t his code snippet is crying out for Tem plat e Met hod. So let 's use t he refact oring called Form Tem plat e Met hod [ Kerievsky R2P] . The basic idea is t o not have a public Execute() in t he subclasses any longer, but t o m ove t hat responsibilit y t o t he superclass. First let 's m ove t hat Execute() m et hod t o t he superclass. I t could t hen look like t his: //The batchprogram baseclass public void Execute() { _LogStart(); DoTheWork(); _LogEnd(); }
The _LogStart() and _LogEnd() m et hods are now privat e inst ead of prot ect ed. The DoTheWork() m et hod is defined in t he following way in order t o force t he subclasses t o im plem ent it : protected abstract void DoTheWork();
Finally, t he DoTheWork() im plem ent at ion in t he subclass could look like t his: //A subclass to a batchprogram baseclass protected override void DoTheWork() { _DoThis(); _DoThat(); }
This is pret t y m uch t he ot her way around. I nst ead of t he subclass calling up t o t he superclass, now t he superclass calls down t o t he subclass. I f t he variat ion point s are good, t hen t his is a very powerful solut ion, yet clean and sim ple! The subclass can t ot ally forget about t he infrast ruct ure ( such as logging) and focus on t he int erest ing and specific st uff, which is t o im plem ent DoTheWork(). ( Again, rem em ber t hat t his was a scaled- down version. I n t he real- world case t hat t his was based on, t here were several m et hods t hat t he subclasses could im plem ent , not j ust one.) Anot her problem where it is hard t o know whet her t o use t he solut ion up front or not is t he St at e pat t ern [ GoF Design Pat t erns] . I t 's oft en a m at t er of over- design if you apply it from t he beginning. I nst ead, it 's oft en bet t er t o wait unt il you know t hat it 's a good idea because you have t he problem . But I t alked quit e a lot about t he St at e pat t ern in Chapt er 2, " A Head St art on Pat t erns," so I 'm sure you have a good feeling for how t o approach it . So we have discussed refact oring t o pat t erns a bit now. I m ent ioned refact oring from pat t erns as well, so let 's t ake one such exam ple.
Prevent Growing Problems Example Two: Inline Singleton More and m ore oft en t he Singlet on pat t ern [ GoF Design Pat t erns] is considered a worst pract ice. The reason for t hat is you creat e global inst ances, which are oft en a problem in t hem selves. Anot her problem is t hat singlet ons oft en m ake t he t est s harder t o writ e ( rem em ber, t est abilit y is crucial) , and t he int eract ion wit h t he singlet ons isn't very clear in t he code. Let 's assum e you have a code snippet t hat goes like t his: public void DoSomething() { DoThis(); DoThat(); MySingleton.Instance().Execute(); }
The refact oring called I nline Singlet on [ Kerievsky R2P] doesn't go " exact ly" like t his, but t his is a variant . I nst ead of let t ing t he DoSomething() m et hod call t he global variable ( t he singlet on it self) , t he DoSomething() m et hod could ask for t he inst ance as a param et er. Anot her t ypical solut ion is for DoSomething() t o expect t o find t he inst ance as a privat e m em ber t hat has been inj ect ed at const ruct ion t im e or via a set t er before t he call t o DoSomething() is m ade. Then DoSomething() goes like t his: public void DoSomething() {
DoThis(); DoThat(); _nowMyPrivateMember.Execute(); }
So t he singlet on is gone, and t est abilit y is m uch bet t er!
N ot e To be fair, singlet ons can be used wit hout affect ing t est abilit y in t he previous code if t he singlet on is inj ect ed by t he consum er. Then you can use a t est obj ect during t est ing, and t he real singlet on during runt im e.
Problem s? Well, t he m ost apparent problem is t hat if you need t he value way down in t he call st ack, t he param et er is sent a long way before it 's used. On t he ot her hand, it 's very clear where t he value is used, and t his m ight be a sign of sm elly code in it self t hat m ight need som e refact oring. So singlet ons m ight act ually hide sm elly code. I t 's also t he case t hat Dependency I nj ect ion ( which will be discussed in Chapt er 10) will gracefully reduce t he problem of t oo m any singlet ons.
Summary As a sum m ary, I 'd like t o st ress t he point of TDD + Refact oring = = t rue. They are so very m uch in sym biosis. I n order t o be able t o use refact oring in a safe way, you m ust carry out ext ensive t est s. I f you don't , you will int roduce bugs and/ or you will priorit ize not m aking any changes sim ply for t he sake of m aint ainabilit y, because t he risk of int roducing bugs is j ust t oo large. And when you st op m aking changes because of m aint ainabilit y, your code has slowly st art ed t o degrade. At t he sam e t im e, in order t o use TDD, you will have t o carefully guard your code and keep it clean all t he t im e by applying refact oring. Also, isolat e your unit t est s wit h t he help of st ubs and m ocks. I f you don't also guard t he t est code, t hat code has st art ed t o degrade. TDD + Refact oring is a t ot ally different way of writ ing code com pared t o what m ost of us learned in school. I f you give it a real t ry, it will t ot ally rock your world, and you won't go back. Wit h t hese t ools on t he belt , we are ready for an archit ect ure discussion.
Part II: Applying DDD I n t his part , it 's t im e t o apply DDD. We also prepare t he Dom ain Model for t he infrast ruct ure, and focus on rules aspect s.
Th is pa r t is t h e m ost im por t a n t t h e cor e .
Chapt er 4 A New Default Archit ect ure Chapt er 5 Moving Furt her wit h Dom ain- Driven Design Chapt er 6 Preparing for I nfrast ruct ure Chapt er 7 Let t he Rules Rule
Chapter 4. A New Default Architecture I n t he past , I 've been pret t y dat a- cent ric in m y applicat ion archit ect ures. Lat ely, t his has gradually changed and m y current default archit ect ure is based on a Dom ain Model. This is why t he t it le of t his chapt er is " A New Default Archit ect ure." New t o whom , you m ight wonder. I t depends. When you describe Dom ain Model t o som e, t hey will say " Yes? I sn't t hat how it 's always done?" At t he sam e t im e, anot her large group of developers will say " Hm m m ... Will t hat work in realit y?" No m at t er in which of t hose t wo groups you belong, we are j ust about t o st art a j ourney t oget her exploring t he Dom ain Model, and especially how t o build it in t he spirit of Dom ain- Driven Design ( DDD) . We will st art t he j ourney wit h a short discussion about Dom ain Models and DDD. To becom e m ore concret e, we will discuss a list of requirem ent s of an exam ple applicat ion and sket ch possible solut ions t o different feat ures. To get a bet t er feeling of t he requirem ent s, we will also use anot her view and sket ch an init ial UI . The chapt er will end wit h a discussion about execut ion st yles for Dom ain Models. Let 's get st art ed.
The Basis of the New Default Architecture As I j ust said, m y new default archit ect ure is based on a Dom ain Model. We discussed Dom ain Models a lot in Chapt er 1, " Values t o Value," but let 's discuss it here, also.
N ot e Paul Gielens said t hat a warning is in place regarding t he word " Default " in t he nam e of t his chapt er. The risk is t hat som e readers will get t he feeling t hat t his is t he way t o approach all syst em s. Let m e st ress t hat t his is definit ely not t he case; t his book is not aim ing t o be a t em plat e or som et hing like t hat . Absolut ely not ! I t 's in t he spirit of DDD t o let t he business problem govern what your solut ion looks like!
When one says " Dom ain Model" in t he cont ext of archit ect ure, what is usually m eant is usage of t he Dom ain Model pat t ern [ Fowler PoEAA] . I t 's like old school obj ect - orient at ion, in t hat you t ry t o m ap a sim plified view ( or m ore correct ly st at ed, t he chosen abst ract ion) of t he realit y from your problem dom ain as closely as possible wit h an obj ect - orient ed m odel. The final code will be very close t o t he chosen abst ract ions. That goes for bot h st at e and behavior, as well as for possible navigat ion pat hs and relat ionships bet ween obj ect s.
N ot e Som eone ( unfort unat ely, I don't rem em ber who) called t he applicat ion of t he Dom ain Model pat t ern in C# " program m ing C# as if it was Sm all- Talk." To som e, t hat m ight not sound posit ive at first , but it was said and m eant as a very posit ive t hing.
The Dom ain Model is not t he silver bullet , but it cert ainly com es wit h several posit ive propert ies, especially for large- scale and/ or com plex applicat ions t hat are long last ing. And as Mart in Fowler says [ Fowler PoEAA] , if you st art using Dom ain Model for one proj ect , you will probably find yourself want ing t o use it even for sm all, sim ple applicat ions lat er.
How to Recognize If an Application Will Be Long Lasting Speaking of long- last ing applicat ions, I especially rem em ber one part icular applicat ion I was asked t o build. A global dat a com m unicat ion service provider asked m e t o build a prelim inary help desk applicat ion t o be used at t heir new office in Sweden. Tim e was t ight t hey needed it t he next day so t hat t hey weren't sit t ing t here wit h paper and pen in t heir high- t ech office when t he press cam e t o t he opening day. Because it was going t o be exchanged for a com pany policy st andard applicat ion j ust a few weeks lat er, however, t hey t old m e t hat building it well wasn't im port ant . Yep, you guessed right . The quick- and- dirt y applicat ion I built was st ill being used in product ion five years lat er. Sure, we im proved it a lot , but I couldn't convince t hem t o change from t he plat form I st art ed using t o som et hing m ore suit able for a m issioncrit ical applicat ion.
From Database Focus to Domain Model Focus My m ove from dat abase focus t o Dom ain Model focus was m ainly because I changed m y em phasis from efficiency t o m aint enance. I t 's not t hat m y new design st yle has t o sacrifice efficiency, but I t ry t o avoid t hose prem at ure opt im izat ions of every lit t le det ail t hat oft en cost m ore t han t hey are wort h. I nst ead, I t ry t o creat e as clear a Dom ain Model as possible, which m akes it easier t o do opt im izat ions when really needed. I t 's not t hat I 'm forget t ing about t he dat abaseit 's j ust not m y first focus. I t ry t o find suit able com prom ises, so I do t hink it 's im port ant t o deal wit h t he dat abase neat ly. A Dom ain Model- focused design is m ore m aint ainable in t he long run because of it s increased clarit y and an im plem ent at ion t hat is m ore t rue t o t he abst ract ions of t he dom ain. Anot her very im port ant reason is t hat a powerful Dom ain Model is a good t ool for decreasing duplicat ion of logic. ( Those are all propert ies of obj ect - orient at ion as well. I see Dom ain Model as a st yle where obj ect - orient at ion is used in a pure way, pushing t he lim it s t o writ e m aint ainable code. Oh, and t o increase t est abilit y.) When you hear t he words " Dom ain Model pat t ern" [ Fowler PoEAA] , it m ight m ean som et hing very specific t o you, but of course t here are a lot of variat ions on how t o apply t hat pat t ern. Let 's call t he variat ions different st yles.
More Specifically, a DDD Focus This leads m e on t o what I consider t he m ain inspirat ion of m y current favorit e Dom ain Model st yle, t hat of DDD by Eric Evans [ Evans DDD] . As you already know from t he lengt hy discussion in Chapt er 1, DDD is several different t hings; for exam ple, it is a set of pat t erns for helping out wit h st ruct uring t he Dom ain Model. We will get st art ed in applying t hose t ools really soon. But before focusing on t he det ails, I 'd like t o t ake a quick look at layering according t o DDD.
Layering According to DDD
I n m y previous book [ Nilsson NED] , I t alked a lot about a rigorous layering schem e, and I t hink I need t o say at least a few words about how I t hink about layering t oday. I n one way, I st ill st ress layering a lot . Because I 'm very m uch int o DDD, I t ry hard t o m ove I nfrast ruct ure, such as persist ence, int o a layer of it s own, away from t he core Dom ain layer. On t he ot her hand, I 'm m ore relaxed about layering and focus a lot of t he effort s on a single one: t he Dom ain layer. I won't split t hat layer, t he Dom ain Model, int o finer layers; I let it be pret t y coarsegrained. ( As a m at t er of fact , som e of t he DDD pat t erns com e in handy for som e of t he problem s I previously used layering for.) We have discussed t wo layers so far: I nfrast ruct ure and Dom ain. On t op of t hese t here m ight be an Applicat ion layer providing som e coordinat ion, but it 's very t hin, only delegat ing t o t he Dom ain layer. ( See Service Layer pat t ern [ Fowler PoEAA] , which is like scenario classes, delegat ing all t he work t o t he Dom ain Model.) Finally, we have t he UI layer on t op. I drew a sim plified layering schem a exam ple in Figure 4- 1 .
Figu r e 4 - 1 . Typica l la ye r in g for D D D pr oj e ct s
N ot e Again, please don't aut om at ically do anyt hing regarding layering j ust because you read about it in a book! You should challenge every decision!
I t hink t here are few t hings t o not e regarding how I use layering now com pared t o m y old st yle. First of all, t he Applicat ion layer isn't m andat ory; it is only t here if it really adds value. Since m y Dom ain layer is so m uch richer t han before, it 's oft en not int erest ing wit h t he Applicat ion layer.
Anot her difference is t hat inst ead of all calls going down, t he I nfrast ruct ure layer m ight " know" about t he Dom ain layer and m ight creat e inst ances t here when reconst it ut ing from persist ence. The point is t hat t he Dom ain Model should be oblivious of t he infrast ruct ure.
N ot e I t is also im port ant t o not e we didn't t ake int o account part it ioning, or slicing t he pieces in t he ot her dim ension com pared t o layering. This is im port ant for large applicat ions. We get back t o t his in Chapt er 10, " Design Techniques t o Em brace."
I t 's t im e t o st art our j ourney and t o t ry out som e of t he concept s we have been discussing in t his and earlier chapt ers, wit h a st rong focus on t he Dom ain layer. I t hink a good way of doing t hat is t o set up an exam ple.
A First Sketch To m ake it a bit m ore concret e, let 's use a list of problem s/ feat ures t o discuss how som e com m on design problem s of an ordering applicat ion could be solved. I know, I know. The answer t o how I would deal wit h t he feat ure list wit h m y current favorit e st yle of a Dom ain Model depends on m any fact ors. As I said before, t alking about a " default archit ect ure" is st range because t he archit ect ure m ust first and forem ost depend on t he problem at hand. However, I st ill want t o discuss a possible solut ion here. I t will be very m uch a sket ched sum m ary, wit h det ails following in lat er chapt ers. What I will be discussing here will be kind of a first rough at t em pt , and I will leave it like t hat , let t ing t he design evolve in lat er chapt ers.
Problems/Features for Domain Model Example I will reuse t his list of requirem ent s in lat er chapt ers for discussing cert ain concept s. Let 's dive in and exam ine t he det ails for each problem / feat ure in random order.
1 . List cust om ers by applying a flexible and com plex filt er. The cust om er support st aff needs t o be able t o search for cust om ers in a very flexible m anner. They need t o use wildcards on num erous fields such as nam e, locat ion, st reet address, reference person, and so on. They also need t o be able t o search for cust om ers wit h orders of a cert ain kind, wit h orders of a cert ain size, wit h orders for cert ain product s, and so on. What we're t alking about here is a full- fledged search ut ilit y. The result is a list of cust om ers, each wit h a cust om er num ber, cust om er nam e, and locat ion. 2 . List t he orders when looking at a specific cust om er. The t ot al value for each order should be visible in t he list , as should t he st at us of t he order, t ype of order, order dat e, and nam e of reference person. 3 . An order can have m any different lines. An order can have m any order lines, where each line describes a product and t he num ber of t hat product t hat has been ordered. 4 . Concurrency conflict det ect ion is im port ant . I t 's alright t o use opt im ist ic concurrency cont rol. That is, it 's accept ed t hat when a user is not ified aft er he or she has done som e work and t ries t o save, t here will be a conflict wit h a previous save. Only conflict s t hat lead t o real inconsist encies should be considered conflict s. So t he solut ion needs t o decide on t he versioning unit for cust om ers and for orders. ( This will slight ly affect som e of t he ot her feat ures.) 5 . A cust om er m ay not owe us m ore t han a cert ain am ount of m oney. The lim it is specific per cust om er. We define t he lim it when t he cust om er is added init ially, and we can change t he lim it lat er on. I t 's considered an inconsist ency if we have unpaid orders of a
t ot al value of m ore t han t he lim it , but we allow t hat inconsist ency t o happen in one sit uat ion, t hat is if a user decreases t he lim it . Then t he user t hat decreases t he lim it is not ified, but t he save operat ion is allowed. However, an order cannot be added or changed so t hat t he lim it is ex ceeded. 6 . An order m ay not have a t ot al value of m ore t han one m illion SEK ( SEK is t he Swedish currency, and I 'm using it for t his exam ple) . This lim it ( unlike t he previous one) is a syst em - wide rule. 7 . Each order and cust om er should have a unique and user- friendly num ber. Gaps in t he series are accept able. 8 . Before a new cust om er is considered accept able, his or her credit will be checked wit h a credit inst it ut e. That is, t he lim it discussed in st ep 5 t hat is defined for a cust om er will be checked t o see if it 's reasonable. 9 . An order m ust have a cust om er; an order line m ust have an order. There m ust not be any orders wit h an undefined cust om er. The sam e goes for order lines: t hey m ust belong t o an order. 1 0 . Saving an order and it s lines should be at om ic. To be honest , I 'm not act ually sure t hat t his feat ure is necessary. I t m ight be alright if t he order is creat ed first and t he order lines are added lat er, but I want t he rule for t his exam ple so t hat we have a feat ure relat ed t o t ransact ional prot ect ion. 1 1 . Orders have an accept ance st at us t hat is changed by t he user. This st at us can be changed by users bet ween different values ( such as t o approved/ disapproved) . To ot her st at us values, t he change is done im plicit ly by ot her m et hods in t he Dom ain Model. So we now have a nice, sim ple lit t le feat ure list t hat we can use for discussing solut ions from an overview perspect ive.
N ot e You m ight get t he feeling t hat t he feat ure list is a bit t oo dat a focused. I n som e cases, what m ight seem dat a focused will in pract ice be solved wit h propert ies t hat have som e behavior at t ached. But t he im port ant t hing t o not e here is t hat what we want is t o apply t he Dom ain Model. The m ain problem wit h t hat is how t o deal wit h dat a if we use a relat ional dat abase. Therefore, it 's m ore im port ant t o focus a bit m ore on t he dat a t han norm al when it com es t o obj ect - orient at ion.
Wit h t he problem s/ feat ures in place, it 's t im e t o discuss a possible solut ion for how t o deal wit h t hem .
Dealing with Features One by One What does all t his com e down t o? Let 's find out by exam ining how I would t ypically solve t he problem s/ feat ures list ed earlier in t his chapt er. I will focus solely on t he Dom ain Model so t hat we concent rat e on t he " right " t hings for now and t ry t o cat ch and form t he Ubiquit ous Language wit hout get t ing dist ract ed by t he infrast ruct ure or ot her layers. St ill, it can be good t o have som e idea about t he t echnical environm ent as well. We keep down t he t echnical com plexit y and decide t hat t he cont ext is a Rich GUI applicat ion execut ing at t he client deskt ops t oget her wit h t he Dom ain Model and a physical dat abase server wit h a relat ional dat abase, all on one LAN.
N ot e I 'm going t o m ent ion loads of pat t erns everywhere, but please not e t hat we will com e back t o t hose in lat er chapt ers and t alk and show a lot about t hem t hen. ( See Appendix B.)
Wit hout furt her ado...
1. List Customers by Applying a Flexible and Complex Filter The first requirem ent is pret t y m uch about dat a and a piece of search behavior. First , let 's sket ch what t he Customer class and surroundings ( wit hout t he Order for now) could look like ( see Figure 42) .
Figu r e 4 - 2 . Th e Customer cla ss a n d su r r ou n din gs
N ot e Believe it or not , t here's a reason for using hand- drawn figures. I 'm showing early sket ches, j ust a quick idea of what I t hink I can st art wit h. The code is t he execut able, crisp art ifact . The code is t he m ost im port ant represent at ion of t he real m odel.
As you see, I used a sim ple ( naïve) Customer class and let it be a com posit ion of Address and ReferencePersons. I prefer t o not put t his kind of search behavior direct ly on t he Customer class it self, so at t his point I only have dat a on t he classes shown in Figure 4- 2 .
N ot e I could use t he Part y Archet ype pat t ern [ Arlow/ Neust adt Archet ype Pat t erns] inst ead or ot her t ypes of role im plem ent at ions, but let 's use sim ple st ruct ures for now.
We could solve t he first requirem ent by using a Query Obj ect [ Fowler PoEAA] , which I t alked about in Chapt er 2, " A Head St art on Pat t erns," but in pract ice I have found t hat it 's nice t o encapsulat e t he use of Query Obj ect s a bit if possible. There are oft en a lot of t hings t o define regarding t he result of queries t hat aren't j ust relat ed t o t he crit eria. I 'm t hinking about sort order, ot her hidden crit eria, opt im izat ions, where t o send t he Query Obj ect for execut ion, and so on. I t herefore prefer t o use a Reposit ory [ Evans DDD] for encapsulat ing t he query execut ion a bit . I t also cut s down in code verbosit y for t he consum ers and increases t he explicit ness in t he program m ing API . I n Chapt er 2, we discussed t he Fact ory pat t ern [ Evans DDD] and said it was about t he st art of t he lifecycle of an inst ance. The Reposit ory t akes care of t he rest of t he lifecycle, aft er t he creat ion unt il t he " deat h" of t he inst ance. For exam ple, t he Reposit ory will bridge bet ween t he dat abase and t he Dom ain Model when you want t o get an inst ance t hat has been persist ed before. The Reposit ory will t hen use t he infrast ruct ure t o fulfill it s t ask. I could let t he m et hod on t he Reposit ory t ake a huge list of param et ers, one for each possible filt er field. That is a sure sign of sm elly code, however: code t hat 's not going t o be very clear or easy t o m aint ain. What 's m ore, a param et er of st ring t ype called " Nam e" is hard t o underst and for t he Reposit ory regarding how t he param et er should be used, at least if t he st ring is em pt y. Does it m ean t hat we are looking for cust om ers wit h em pt y nam es or t hat we don't care about t he nam es? Sure, we can invent a m agic st ring t hat m eans t hat we are looking for cust om ers wit h em pt y nam es. Or slight ly bet t er, we force t he user t o add an ast erisk if he or she isn't int erest ed in t he nam e. Anyway, neit her is a very good solut ion. On t he ot her hand, going for a Query Obj ect [ Fowler PoEAA] , especially a dom ain- specific one, or t he Specificat ion pat t ern [ Evans DDD] is t o st art by going t oo far, t oo quickly, don't you t hink? Let 's go for t he sim plest possible and only consider t wo of all possible crit eria for now. See Figure 4- 3 for an exam ple of how t his could be done.
Figu r e 4 - 3 . A CustomerRepository for de a lin g w it h fle x ible cr it e r ia
N ot e Of course, we m ight use a Query Obj ect , for exam ple, inside t he Reposit ory.
DDD and TDD are a great com binat ion. You get inst ant feedback, and t rying out t he m odel wit h t est code is a great way of gaining insight . Wit h t hat said, I 'm not going t he TDD rout e here and now. I will explain t he m odel sket ch wit h som e t iny t est s here, but only as a way of providing anot her view of t he solut ion idea. I n t he next chapt er, we will t ake a st ep back and m ore t horoughly and t rue t o TDD t ry out and develop t he m odel. So t o fet ch all cust om ers in a cert ain t own ( Ronneby) , wit h at least one order wit h a value of > 1,000 SEK, t he following t est could be used: [Test] public void CanGetCustomersInSpecificTownWithOrdersOfCertainSize() { int numberOfInstancesBefore = _repository.GetCustomers ("Ronneby", 1000).Count; _CreateACustomerAndAnOrder("Ronneby", 20000); Assert.AreEqual(numberOfInstancesBefore + 1 , _repository.GetCustomers ("Ronneby", 1000).Count); }
As you saw in t he exam ple, t he Reposit ory m et hod GetCustomers() is used t o fet ch all t he cust om ers t hat fulfill t he crit eria of t own and m inim um order size.
2. List the Orders When Looking at a Specific Customer I could go for a bidirect ional relat ionship bet ween Customer and Order. That is, each Customer has a list of Orders, and each Order has one Customer . But bidirect ionalit y cost s. I t cost s in com plexit y, t ight coupling, and overhead. Therefore, I t hink it 's good enough t o be able t o get t o t he Orders for a Customer via t he OrderRepository. This leads t o t he m odel shown in Figure 4- 4 .
Figu r e 4 - 4 . OrderRepository, Order, a n d su r r ou n din gs
So when t he consum er want s t o look at t he Orders for a cert ain Customer , he has t o ask t he OrderRepository like t his: [Test] public void CanGetOrdersForCustomer() { Customer newCustomer = _CreateACustomerAndAnOrder ("Ronneby", 20000); IList ordersForTheNewCustomer = _repository.GetOrders(newCustomer); Assert.AreEqual(1, ordersForTheNewCustomer.Count); }
I could let t he Customer have an OrderList propert y and im plicit ly t alk t o t he Reposit ory, but I t hink t he explicit ness is bet t er here. I also avoid coupling bet ween t he Customer and t he OrderRepository. The t ot al value of each order is wort h a separat e m ent ion. I t 's probably t rickier t han first expect ed as t he calculat ion needs all orderLines of t he order. Sure, t hat 's not t ricky, but it does alarm m e. I know t his is prem at ure, but I can't help it ( because loading t he orderLines will be expensive when you j ust want t o show a pret t y large list of orders for a cert ain customer , for exam ple) . I t hink one helpful st rat egy is t hat if t he orderLines aren't loaded, a sim ple am ount field, which probably is st ored in t he Orders t able, is used for t he TotalAmount propert y. I f orderLines are loaded, a com plet e calculat ion is used inst ead for t he TotalAmount propert y. This isn't som et hing we need t o t hink about right now. And no m at t er what , for consum ers, it 's as sim ple as t his: [Test] public void CanGetTotalAmountForOrder() { Customer newCustomer = _CreateACustomerAndAnOrder ("Ronneby", 420);
Order newOrder = (Order)_repository.GetOrders (newCustomer)[0]; Assert.AreEqual(420, newOrder.TotalAmount); }
I n m y opinion, t he " right " way t o t hink about t his is t hat lines are t here because we work wit h an Aggregat e and t he lines are an int im at e part of t he order. That m ight m ean t hat perform ance would suffer in cert ain sit uat ions. I f so, we m ight have t o solve t his wit h t he Lazy Load pat t ern [ Fowler PoEAA] ( used t o load list s from t he dat abase j ust in t im e, for exam ple) or a read- only, opt im ized view. But , as I said, t hat 's som et hing we should deal wit h when we have found out t hat t he sim ple and direct solut ion isn't good enough.
3. An Order Can Have Many Different Lines Feat ure 3 is pret t y st raight forward. Figure 4- 5 describes t he enhanced m odel.
Figu r e 4 - 5 . Order a n d OrderLines
Not e t hat I 'm considering a unidirect ional relat ionship here, t oo. I can probably live wit h sending bot h an Orderline and it s Order if I know t hat I need bot h for a cert ain piece of logic. The risk of sending an order t oget her wit h a line for anot her order is probably pret t y sm all. [Test] public void CanIterateOverOrderLines() { Customer newCustomer = _CreateACustomerAndAnOrder ("Ronneby", 420); Order newOrder = (Order)_repository.GetOrders (newCustomer)[0];
foreach (OrderLine orderLine in newOrder.OrderLines) return; Assert.Fail("I shouldn't get this far"); }
Also not e t he big difference in t he m odel shown in Figure 4- 5 com pared t o how a relat ional m odel describing t he sam e t hing would look. I n t he relat ional m odel, t here would be a one- t o- m any relat ionship from Product t o OrderLine and m any- t o- one, but so far we t hink t hat t he one- t o- m any relat ionship isn't really int erest ing in t his part icular Dom ain Model.
4. Concurrency Conflict Detection Is Important Hm m m ...feat ure 4 is t ricky. I t hink a reasonable solut ion here is t hat Customer is on it s own, and Order t oget her wit h OrderLines is a concurrency unit of it s own. That fit s in well wit h m aking it possible t o use t he Dom ain Model for t he logic needed for feat ures next . I use t he pat t ern called Aggregat e [ Evans DDD] for t his, which m eans t hat you decide what obj ect s belongs t o t he cert ain clust ers of obj ect s t hat you t ypically work wit h as single unit s, loading t oget her ( by default , at least for writ e scenarios) , evaluat ing rules for t oget her, and so on. I t 's im port ant t o not e, t hough, t hat t he Aggregat e pat t ern isn't first and forem ost a t echnical pat t ern, but rat her should be used where it adds m eaning t o t he m odel. Concurrency unit is one t hing Aggregat e is used for. ( See Figure 4- 6 .)
Figu r e 4 - 6 . Aggr e ga t e s
5. A Customer May Not Owe Us More Than a Certain Amount of Money Feat ure 5 is also pret t y t ricky. The first solut ion t hat springs t o m ind is probably t o add a TotalCredit propert y on t he Customer . But t his is problem at ic because it 's t hen a bit t oo t ransparent , so t hat t he consum er sees no cost at calling t he propert y. I t 's also a consist ency issue. I 'm not even seeing t he Customer Aggregat e as having t he Orders, so I don't expect t he unit t o be t he correct one for assum ing consist ency handling direct ly in t he Dom ain Model, which is a good sign t hat I should look for anot her solut ion. I t hink I need a Service [ Evans DDD] in t he Dom ain Model t hat can t ell m e t he current t ot al credit for a cust om er. You'll find t his described in Figure 4- 7
Figu r e 4 - 7 . TotalCreditService
The reason for t he overload t o GetCurrentCredit() of TotalCreditService is t hat I t hink it would be nice t o be able t o check how large t he current credit is wit hout considering t he current order t hat is being creat ed/ changed. At least t hat 's t he idea. Let 's see how t he service could look in act ion ( and skipping t he int eract ion here bet ween t he different pieces) . [Test]
public void CanGetTotalCreditForCustomerExcludingCurrentOrder() { Customer newCustomer = _CreateACustomerAndAnOrder ("Ronneby", 22); Order secondOrder = _CreateOrder(newCustomer, 110); TotalCreditService service = new TotalCreditService(); Assert.AreEqual(22+110 , service.GetCurrentCredit(newCustomer)); Assert.AreEqual(22, service.GetCurrentCredit(newCustomer, secondOrder)); }
I t hink you can get a feeling about t he current idea of t he cooperat ion bet ween t he Order, t he Customer , and service from Figure 4- 7 and t he previous snippet .
6. An Order May Not Have a Total Value of More Than One Million SEK Because I est ablished wit h t he concurrency conflict det ect ion feat ure t hat an Order including it s OrderLines is a concurrency conflict det ect ion unit of it s own or m ore concept uallyan Aggregat e, I can deal wit h t his feat ure in t he Dom ain Model. An Aggregat e invariant is t hat t he Order value m ust be one m illion SEK or less. No ot her user can int erfere, creat ing a problem regarding t his rule for a cert ain order, because t hen I or t he ot her user will get a concurrency conflict inst ead. So I creat e an IsOKAccordingToSize() m et hod on t he Order t hat can be called by t he consum er at will. See Figure 48 for m ore inform at ion.
Figu r e 4 - 8 . Order w it h IsOKAccordingToSize() m e t h od
A sim ple t est for showing t he API could look like t his: [Test] public void CanCheckThatAnOrdersTotalSizeIsOK() { Customer newCustomer = _CreateACustomerAndAnOrder ("Ronneby", 2000000); Order newOrder = (Order)_repository.GetOrders (newCustomer)[0]; Assert.IsFalse(newOrder.IsOKAccordingToSize()); }
N ot e I need t o consider if I should use t he Specificat ion pat t ern [ Evans DDD] for t his kind of rule lat er on.
7. Each Order and Customer Should Have a Unique and User-friendly Number As m uch as I dislike t his requirem ent from a developer's perspect ive, as im port ant and com m on it is from t he user's perspect ive. I n t he first it erat ion, t his could be handled well enough by t he dat abase. I n t he case of SQL Server, for exam ple, I 'll use I DENTI TYs. The Dom ain Model doesn't have t o do anyt hing except refresh t he Ent it y aft er it has been insert ed int o t he t able in t he dat abase. Before t hen, t he Dom ain Model should probably show 0 for t he OrderNumber and CustomerNumber ( see Figure 4 - 9) .
Figu r e 4 - 9 . En h a n ce d Order a n d Customer
Hm m m ...I already regret t his sket ch a bit . This m eans t hat I allocat e an OrderNumber as soon as t he Order is persist ed t he first t im e. I 'm not so sure t hat 's a good idea. I 'm act ually int erm ingling t wo separat e concept s, nam ely t he I dent it y Field pat t ern [ Fowler PoEAA] for coupling an inst ance t o a dat abase row ( by using t he prim ary key from t he dat abase row as a value in t he obj ect ) and t he I D t hat has business m eaning. Som et im es t hey are t he sam e, but quit e oft en t hey should be t wo different ident ifiers. I t also feels st range t hat as soon as a cust om er is per sist ed it get s a user- friendly num ber kind of as a m arker t hat it is OK. Shouldn't it rat her be when a cust om er is operat ional in t he syst em ( aft er credit checks and perhaps m anual approval) ? I 'm sure we'll find a reason t o com e back t o t his again in lat er chapt ers.
8. Before a New Customer Is Considered OK, His or Her Credit Will Be Checked with a
Credit Institute So we need anot her Service [ Evans DDD] in t he Dom ain Model t hat encapsulat es how we com m unicat e wit h t he credit inst it ut e. See Figure 4- 10 for how it could look.
Figu r e 4 - 1 0 . CreditService
Again, t he idea is a m at t er of cooperat ion bet ween an Ent it y ( Customer in t his case) and a Service for let t ing t he Ent it y answer a quest ion. Let 's see how t he cooperat ion could be dealt wit h in a t est . ( Also not e t hat t he real service will probably use t he OrganizationNumber of t he Customer , which is not t he sam e as t he CustomerNumber, but t he official ident ificat ion provided by t he aut horit ies for regist ered com panies.) [Test] public void CantSetTooHighCreditLimitForCustomer() { Customer newCustomer = _CreateACustomer("Ronneby'); //Inject a stubbed version of CreditService //that won't allow a credit of more than 300. newCustomer.CreditService = new StubCreditService(300); newCustomer.CreditLimit = 1000; Assert.IsFalse(newCustomer.HasOKCreditLimit); }
N ot e Gregory Young point ed t his out as a code sm ell. The problem is m ost likely t o be t hat t he operat ion isn't at om ic. The value is first set ( and t herefore t he old value is overwrit t en) and t hen checked ( if rem em bered) . Perhaps som et hing like Customer.RequestCreditLimit(1000)
would be a bet t er solut ion. We get back t o sim ilar discussions in Chapt er 7, " Let t he Rules Rule."
9. An Order Must Have a Customer; an Orderline Must Have an Order Feat ure 9 is a com m on and reasonable requirem ent , and I t hink it 's sim ply and best dealt wit h by referent ial int egrit y const raint s in t he dat abase. We can, and should, t est t his in t he Dom ain Model, t oo. There's no point in sending t he Dom ain Model changes t o persist ence if it has obvious incorrect ness like t his. But inst ead of checking for it , as t he first t ry I m ake it kind of m andat ory t hanks t o an OrderFactory class as t he way of creat ing Orders, and while I 'm at it I deal wit h OrderLines t he sam e way so an OrderLine m ust have Order and Product ( see Figure 4- 11) .
Figu r e 4 - 1 1 . OrderFactory a n d e n h a n ce d Order
Let 's t ake a look at t he int eract ion bet ween t he different part s in a t est as usual. [Test] public void CanCreateOrderWithOrderLine() { Customer newCustomer = _CreateACustomer("Karlskrona"); Order newOrder = OrderFactory.CreateOrder(newCustomer); //The OrderFactory will use AddOrderLine() of the order. OrderFactory.CreateOrderLine(newOrder, new Product()); Assert.AreEqual(1, newOrder.OrderLines);
}
Hm m m ... This feels quit e a bit like overdesign and not exact ly fluent and sm oot h, eit her. Let 's see what we t hink about it when we really get going in t he next chapt er. While we are discussing t he OrderFactory, I want t o m ent ion t hat I like t he idea of using Null Obj ect s [ Woolf Null Obj ect ] for OrderType, Status, and ReferencePerson. ( That goes bot h for t he Dom ain Model and act ually also for t he underlying relat ional dat abase.) The Null Obj ect pat t ern m eans t hat inst ead of using null, you use an em pt y inst ance ( where em pt y m eans default values for t he m em bers, such as string.Empty for t he strings) . That way you can always be sure t o be able t o " follow t he dot s" like t his: this.NoNulls.At.All.Here.Description
When it com es t o t he dat abase, you can cut down on out er j oins and use inner j oins m ore oft en because foreign keys will at least point t o t he null sym bols and t he foreign key colum ns will be nonnullable. To sum m arize, null obj ect s increase t he sim plicit y a lot and in unexpect ed ways as well. As you saw in Figure 4- 11, I had also added a m et hod t o t he Order class, AddOrderLine(), for adding OrderLines t o t he Order. That 's part of an im plem ent at ion of t he Encapsulat e Collect ion Refact oring [ Fowler R] , which basically m eans t hat t he parent will prot ect all changes t o t he collect ion. On t he ot her hand, t he dat abase is t he last out post , and I t hink t his rule fit s well t here, t oo.
10. Saving an Order and Its Lines Should Be Atomic Again, I see Order and it s OrderLines as an Aggregat e, and t he solut ion I plan t o use for t his feat ure will be orient ed around t hat . I will probably use an im plem ent at ion of t he Unit of Work pat t ern [ Fowler PoEAA] for keeping t rack of t he inst ances t hat have been changed, which are new, and which are delet ed. Then t he Unit of Work will coordinat e t hose changes and use one physical dat abase t ransact ion during persist ence.
11. Orders Have an Acceptance Status As we specified, orders have an accept ance st at us ( see Figure 4- 12) . Therefore, I j ust add a m et hod called Accept() . I leave t he decision about whet her or not t o int ernally use t he St at e pat t ern [ GoF Design Pat t erns] for lat er. I t is bet t er t o m ake im plem ent at ion decisions like t his during refact oring.
Figu r e 4 - 1 2 . Order a n d Status
When we discuss ot her st at es for Order, we add m ore m et hods. For now, not hing else has been explicit ly required so we st ay wit h j ust Accept() . The current idea could look like t his: [Test] public void CanAcceptOrder() { Customer newCustomer = _CreateACustomer("Karlskrona"); Order newOrder = OrderFactory.CreateOrder(newCustomer); Assert.IsFalse(newOrder.Status == OrderStatus.Accepted); newOrder.Accept(); Assert.IsTrue(newOrder.Status == OrderStatus.Accepted); }
The Domain Model to This Point So if we sum m arize t he Dom ain Model j ust discussed, it could look like Figure 4- 13.
Figu r e 4 - 1 3 . A sk e t ch e d D om a in M ode l for h ow I n ow t h in k I w ill a ppr oa ch t h e fe a t u r e list [View full size image]
N ot e The class ReferencePerson is in t wo different Aggregat es in Figure 4- 13, but t he inst ances aren't . That 's an exam ple of how t he st at ic class diagram lacks in expressiveness, but also an exam ple t hat is sim ply explained wit h a short com m ent .
What 's t hat ? Messy? OK, I agree, it is. We'll part it ion t he m odel bet t er when we dive int o t he det ails furt her.
N ot e The m odel shown in Figure 4- 13 and all t he m odel fragm ent s were creat ed up- front , and t hey are bound t o be im proved a lot when I m ove on t o developing t he applicat ion. Anot her not e is t hat I only showed t he core Dom ain Model and not t hings t hat are m ore infrast ruct ure relat ed, such as t he m ent ioned Unit of Work [ Fowler PoEAA] . Of course, t hat was t ot ally on purpose. We'll get back t o infrast ruct ure lat er in t he book. Now I 'm focusing on t he Dom ain Model.
I said t hat t here are lot s of variat ions of how t he Dom ain Model pat t ern is used. To show you som e ot her st yles, I asked a couple of friends of m ine t o describe t heir favorit e ways of applying Dom ain Models. You'll find t hose in Appendix A. To give us a bet t er feeling of t he requirem ent s, I 'd like t o t ake a look at t hem from yet anot her view and sket ch a few form s for an upcom ing UI .
Making a First Attempt at Hooking the UI to the Domain Model I once st art ed describing a new archit ect ure from t he bot t om up. I m ean, I st art ed describing it from t he dat abase. One of t he readers t old m e in no uncert ain t erm s t hat I should st art t alking about what t he archit ect ure looked like from t he UI program m er's point of view. I f it wasn't good in t hat perspect ive, t here was no point reading on. I decided t hat t hat way of t hinking had it s m erit s, so it 's now t im e t o have a first look at t he Dom ain Model we j ust sket ched from a UI program m er's viewpoint .
A Basic Goal I want you t o consider whet her you t hink we are fulfilling one of m y basic goals, which is providing " sim plicit y t o t he left " ( or " sim plicit y t o t he t op," depending upon how you visualize t he layers in a layered archit ect ure) . What I m ean is providing a sim ple API for t he UI program m er so he or she can easily see, breat he, and underst and t he m odel, and can focus on UI m at t ers and not have t o t hink about com plex prot ocols for t he Dom ain Model. Skipping dat a binding is not a basic goal, not at all, but I will skip dat a binding here in t he first discussion about t he UI . Dat a binding won't be t he focus at all in t his book, but we'll t ouch som e m ore on t hat in lat er chapt ers.
The Current Focus of the Simple UI I t hink t he Dom ain Model m ight be som ewhat abst ract at t he m om ent , but discussing it from a UI point of view m ight change t his a bit . The scenarios I t hink we should t ry out are t he following: List orders for a cust om er Add an order Again, I won't use ordinary dat abinding right here, but j ust sim ple, direct code for hooking t he UI t o t he Dom ain Model.
List Orders for a Customer I need t o be able t o list orders for a cust om er. When t he Dom ain Model is built lat er on, I j ust need t o add a " view" on t op of t he Dom ain Model and fake som e Orders. I 'm t hinking about a form t hat looks som et hing like t he one in Figure 4- 14.
Figu r e 4 - 1 4 . List or de r s for a cu st om e r
N ot e You m ight t hink t hat I 'm fight ing t he t ool ( VS.NET or sim ilar form edit ors) , but because I 'm in ext rem ely early sket ch m ode, I decided t o visualize t he form ideas wit h pen and paper. ( But for t his book, I 'll provide im ages t hat were rendered in a graphics program .)
This is plain and sim ple. To m ake t his possible t o t est and show, we need t o writ e a funct ion t hat could be called from Main() for faking a cust om er and som e orders. The user will probably choose a cust om er from a cust om er list form , and we provide t he customer inst ance t o t he const ruct or of t he form t o show det ails of a single customer ( let 's call t he form CustomerForm) . The form st ores t he customer inst ance in a privat e field called ( _customer) . Then t he code in _PaintCustomer() m et hod of t he form could look like t his: //CustomerForm private void _PaintCustomer() { //Get the data: IList theOrders = _orderRepository.GetOrders(_customer); //Paint the customer: txtCustomerNumber.Text = _customer.CustomerNumber.ToString(); txtName.Text = _customer.Name; //Paint the orders: foreach (Order o in theOrders) { //TODO...
} }
Som et hing t hat is wort h point ing out is t hat I expect t he usage of Null Obj ect [ Woolf Null Obj ect ] in t he previous code when I paint t he orders. Because of t hat , all orders will have a ReferencePerson ( an em pt y one) , even if it hasn't been decided yet , so I don't need any null checks. I n m y opinion, t his is an exam ple of a det ail t hat shows how t o increase t he sim plicit y for t he UI program m er. Having t o writ e t he code by hand for filling t he UI was a bit t edious. But looking back at it , isn't it ext rem ely sim ple t o read so far? Sure, we are t rying out sim ple t hings, but st ill.
Add an Order The ot her form exam ple is one for m aking it possible t o add an order. I 'm envisioning t hat you have first select ed t he cust om er and t hen you get a form like t he one shown in Figure 4- 15.
Figu r e 4 - 1 5 . For m for a ddin g a n or de r
To m ake t his happen, first an Order is inst ant iat ed, and t hen t he order is sent t o t he const ruct or of t he form .
The code for _PaintOrder() could t hen look like t his: //OrderForm._PaintOrder() txtOrderNumber.Text = _ShowOrderNumber(_order.OrderNumber); txtOrderDate.Text = _order.OrderDate.ToString(); //And so on, the code here is extremely similar in //principle to the one shown above for _PaintCustomer().
To avoid having a zero value for txtOrderNumber, I used a helper ( _ShowOrderNumber() ) t hat perhaps shows " New" inst ead of 0.
What Did We Just See? One way of describing t his t ype of UI is t o use t he pat t ern Fowler calls Separat ed Present at ion [ Fowler PoEAA2] . The idea is t o separat e t he logic from t he code t hat m anipulat es t he present at ion. I t 's a basic principle t hat has been recom m ended for a long t im e; however, it 's very oft en abused. We get it at least t o a degree m ore or less aut om at ically and for free because we use DDD. Again, t his sect ion was m ost ly t o give us a bet t er feeling about t he requirem ent s by t hinking about t hem from anot her perspect ive, t he UI . From now on, we won't discuss m uch about UI - relat ed t hings unt il we get t o Chapt er 11, " Focus on t he UI ." Now let 's m ove on t o yet anot her view regarding t he requirem ent s.
Yet Another Dimension So far in t his chapt er, I have m ost ly t alked about t he logical st ruct ure of t he Dom ain Model, which is j ust one dim ension of t he pict ure, of course. There are ot hers. I 'd also like t o discuss different execut ion st yles for a while. Let 's st art wit h t he deploym ent environm ent set up for t he feat ure list . There, I said t hat we are creat ing a rich client ( WinForm s) applicat ion wit hout an Applicat ion Server. I t could look sim ilar t o Figure 4- 16.
Figu r e 4 - 1 6 . W in For m s a n d n o Applica t ion Se r ve r
For t he sake of t he discussion here, let 's increase t he com plexit y quit e a bit for a m om ent and add a requirem ent of an applicat ion server as shown in Figure 4- 17.
Figu r e 4 - 1 7 . W in For m s a n d Applica t ion Se r ve r
The first quest ion is on what t ier( s) should t he Dom ain Model execut e? Should it only execut e on t he Applicat ion Server and t hen you give out dum b dat a st ruct ures, Dat a Transfer Obj ect s ( DTO) [ Fowler PoEAA] t o t he Consum er t ier? Or should t he Dom ain Model only execut e on t he consum er side, and you fill it by asking for DTOs from t he Applicat ion server? Or perhaps you want t he Dom ain Model t o execut e on bot h t iers? Perhaps t wo different Dom ain Models?
N ot e I t 's im port ant t o t hink about t he purpose of t he applicat ion server, t hat it 's really needed and t hat you really need t o dist ribut e t he Dom ain Model. Rem em ber Fowler's First Law of Dist ribut ed Obj ect Design: Don't dist ribut e your obj ect s! [ Fowler PoEAA]
Let 's assum e for a second t hat t he Dom ain Model should ( at least ) execut e at t he Applicat ion server. Should it t hen be a shared Dom ain Model inst ant iat ion, so t hat t here is a single inst ance for represent ing a single cust om er t o be used by all users? Or should it be one Dom ain Model inst ant iat ion per user or per session so t hat t here are several inst ances represent ing a cert ain cust om er at a cert ain point in t im e? The t hird quest ion is, should t he Dom ain Model inst ant iat ion be st at eful bet ween calls, or should it go away aft er each call? Fourt h, should we t ry t o build up a com plet e Dom ain Model inst ant iat ion by fet ching as m uch dat a as possible at each request , and never let go of inst ances? We have lot s of quest ions and, as always, t he answers depend on t he sit uat ion. One problem wit h t he discussion is t hat I haven't ( in a DDD- ish m anner) decided t oo m uch about t he infrast ruct ure. Anyway, I 'd like t o say a few words about how I usually prefer t o deal wit h t his.
N ot e What I m ean wit h t he t erm " Dom ain Model inst ant iat ion" is a set of inst ances of t he Dom ain Model inst ead of it s classes. Of course, we are sharing t he classes bet ween users m uch m ore oft en t han t he inst ances. Yet I t hink t his t erm adds clarit y t o t he discussion.
Location of the Domain Model First , if I 'm in cont rol of bot h t he consum er and t he Applicat ion Server, I t hink it m ight be fine t o expose and use t he Dom ain Model bot h at t he consum er and t he Applicat ion Server. I f we don't use it at bot h places, t here is a risk of unnecessary work and less power because t hen we m ight creat e t wo sim ilar, but slight ly different , Dom ain Models, one being for t he consum er and one for t he Applicat ion Server. We also need adapt ers t hat can t ransform bet ween t he t wo Dom ain Models. ( The m ost im port ant t hing here is probably t o be very aware of which sit uat ion you haveone m odel or t wo. I f you t hink it 's one but it 's act ually t wo in realit y, you will run int o subt le problem s.) I t 's im port ant t o m ent ion, for com plex scenarios I 'm fond of Present at ion Model [ Fowler PoEAA2] as t he UI - opt im ized view or version of t he Dom ain Model.
Isolating or Sharing Instances At t he applicat ion server, should we have shared Dom ain Model inst ant iat on, per user or per session? Well, I like t he idea of a shared Dom ain Model inst ant iat ion in t heory, but in pract ice I prefer t o st ay away from it m ost oft en. I t get s m uch m ore com plex t han what you would first expect , so I go for a
Dom ain Model inst ant iat ion per user inst ead or act ually usually per session. I t 's m uch sim pler. One of t he m ain problem s wit h a shared set of inst ant iat ed dom ain obj ect s occurs if it has t o execut e in a dist ribut ed fashion, perhaps on t wo applicat ion servers in a " shared not hing" clust er. We t hen have t he problem of dist ribut ed caching, and t hat 's a t ricky problem ( t o m ake an underst at em ent ) , at least if you need real- t im e consist ency. I t is so t ricky t hat I have given up on it for now as far as finding a general solut ion t hat can scale up well. However, for specific sit uat ions, we can find good enough solut ions. Of course, if we have all Dom ain Model inst ances in m em ory, t here are probably fewer sit uat ions where we need t o scale out , at least for efficiency reasons. St ill, if we do have t he problem , it 's a t ough one t o solve in a good way.
N ot e The t erm " shared not hing" clust er needs a short explanat ion. What I 'm aim ing at is t hat t he applicat ion servers aren't sharing eit her CPU, disk, or m em ory. They are t ot ally independent of each ot her, which is beneficial for scalabilit y. To read m uch m ore about t his I recom m end Pfist er's I n Search of Clust ers [ Pfist er Wolfpack] .
Stateful or Stateless Domain Model Instantiation At t he applicat ion server I don't let t he Dom ain Model inst ant iat ion be st at eful, and I t oss it out bet ween request s. On t he consum er side, on t he ot her hand, I t ry t o keep t he Dom ain Model inst ant iat ion around bet ween request s, but oft en not aft er t he use case is done.
Complete or Subset Instantiation of the Domain Model Finally, I don't t ry t o inst ant iat e t he com plet e Dom ain Model. At t he consum er t hat would j ust be plain st upid in cases where t he dat abase is m oderat ely large or bigger. I nst ead, I use t he old way of t hinking t hat says fet ch what you need and not hing else. And when I 'm done wit h it , I t oss it out , m aking room for ot her dat a, and don't hold on t o old dat a for t oo long. On t he ot her hand, when it com es t o st at ic dat a, it 's different . We should t ry t o cache read- only dat a as m uch as possible.
N ot e There is an open source proj ect called Prevayler [ Prevayler] ( and t here are ot hers t hat are sim ilar) t hat support s Dom ain Model inst ant iat ions t hat are st at eful, shared, and com plet e. So t hat m eans m ore or less t hat t he Dom ain Model and t he dat abase are t he sam e t hing. For t hat t o be possible, we need large am ount s of RAM, but t hat 's less of a problem t oday because m em ory prices are falling, and 64- bit m achines are becom ing com m onplace. The idea is t hat it writ es t o a sequent ial log file each t im e t here is a change t o t he dat abase. I f t he power goes, t he Dom ain Model inst ant iat ion can com e back by reading an im age creat ed at a cert ain point in t im e and reading t hrough t he log file. This is pret t y sim ple and ext rem ely efficient execut ion- wise because all dat a is in m em ory.
What we have t hen is basically an obj ect dat abase, but a sim ple one because t here is no need for fault ing or ot her such t hings. All inst ances are in m em ory all t he t im e, and t here is no need for O/ R Mapping from / t o a relat ional dat abase, not even from / t o an obj ect dat abase. There is j ust t he Dom ain Model inst ant iat ion.
OK, from now on in t his book I will leave t he added com plexit y because of t he applicat ion server and j ust assum e t hat we work wit h t he rich client including t he Dom ain Model or a web applicat ion, which again t alks direct ly t o t he Dom ain Model. One reason for t his decision is t hat I t hink t he classic way of using applicat ion servers is in a way disappearing, or at least changing. What 's t he driving force regarding t hat ? Perhaps SOA, but t hat 's anot her st ory ( which we t alk a bit about again in Chapt er 10) .
Summary I know, you want ed t he chapt er t o go on forever, but sadly it 's com e t o an end. We've covered a lot of ground, discussed a " new" default archit ect ure based on Dom ain Models t o quit e an ext ent , and also sket ched how it can be used for an exam ple. Now it 's t im e t o dig deeper int o t he Dom ain Model and invest igat e it furt her and also m ake lot s of changes t o t he init ial sket ch discussed in t his chapt er.
Chapter 5. Moving Further with DomainDriven Design Som e of you m ight be really eager by now and m ight be saying som et hing like " Com e on, let 's see t he t ools and t ricks t hat will show us how t o im plem ent a Dom ain Model and it s surroundings." Sorry, but t hat 's not how t o proceed. On t he cont rary, I want t o creat e t he Dom ain Model wit hout t hinking about t he t ools and infrast ruct ure. That m eans good old OO t hat I used t o laugh about for real- world applicat ions. As you know by now, I have since t hen changed m y m ind. I t 's pret t y m uch a m at t er of design. So far t he book has been pret t y general and abst ract wit h only sket ches of m odels. I t 's t im e t o change t hat now. From here on I will t alk about how t he problem s could be solved in realit y. I n t his chapt er, we will discuss how t o develop and refine t he Dom ain Model t hat was sket ched in t he previous chapt er. We will m ove slowly by applying TDD when we let t he m odel evolve. Please be prepared t hat t he discussion will be a bit " m oving around" and t he progress a bit slow. ( We will change t his way of discussing t he developm ent by t he next chapt er.) Let 's get st art ed wit h t he t hem e for t his chapt er; refining t he Dom ain Model.
Refining the Domain Model Through Simple TDD Experimentation Do you rem em ber t he very early sket ch we had when we left Chapt er 4, " A New Default Archit ect ure" ? You'll find it again in Figure 5- 1 .
Figu r e 5 - 1 . Ea r ly sk e t ch fr om Ch a pt e r 4
I went a bit overboard, providing t oo m any det ails in t he sket ch which I norm ally wouldn't have. Anyway, it 's bet t er now t han never t o really learn som e of t he det ails of t he requirem ent s. Let 's st art im plem ent ing t his now in a TDD- ish m anner and see what issues we need t o address and resolve, and by doing so we'll evolve t he m odel as we go. I t ypically at t ack feat ure by feat ure, but considering t hat we t alked about som e UI sket ches in t he last chapt er, I t hink I 'll st art by t rying t o m ake it possible t o support t hose UI sket ches and lat er on sum m arize what feat ures we covered.
Starting with the Creation of Order and OrderFactory We'll st art out by creat ing t he Order class and t he OrderFactory. I 'll skip t hinking about everyt hing at first except for what is shown in Figure 5- 2 .
Figu r e 5 - 2 . Fir st pa r t t o im ple m e n t , pa r t s of Order a n d OrderFactory
N ot e The first few st eps are necessary t o properly creat e t he dom ain obj ect s. We m ust com plet e t hese st eps before we can get t o t he int erest ing and crucial part , nam ely t est ing and im plem ent ing t he logic t hat our m odel facilit at es.
I 'll get st art ed on writ ing a first t est for t his. I 'll t est so as t o m ake sure t hat I don't get null back when I ask t he fact ory t o creat e a new order. Here goes: [Test] public void CanCreateOrder() { Order o = OrderFactory.CreateOrder(new Customer()); Assert.IsNotNull(o); }
N ot e You m ight wonder why I nam e m y t est s t he way I do? The early convent ion for nam ing xUnit t est s was t o prefix t hem wit h " Test ," but t hat 's not necessary wit h NUnit , for exam ple, because it will underst and t he at t ribut e Test inst ead. This m eans we are free t o nam e t he t est s t he way we want . This also m eans we get t he t ech- t alk out of t he way and can focus t he discussion on dom ain concept s and design. I like nam ing t he t est s as st at em ent s of what t he t est s int end t o verify. That way, t he collect ion of t est nam es becom es a list of what should be possible and what should not .
At first , t he t est looks ext rem ely sim ple. To m ake t he t est com pile, I need t o creat e an em pt y Order class. That 's fine for now. Next , I need t o creat e an OrderFactory class wit h a single m et hod, but our problem s st art cropping up already when writ ing t hat m et hod. One of t he rules defined in Chapt er 4 was t hat an Order always has t o have a cert ain Customer , and t his is solved by giving t hat responsibilit y t o t he OrderFactory, giving t he Order a Customer at creat ion t im e. Therefore, t he
CreateOrder() m et hod of t he OrderFactory needs t o receive a Customer inst ance as a param et er
( which you can see in t he t est code) . No rocket science t here, but we have j ust st art ed working on t he Order Aggregat e and we would like t o forget about everyt hing else right now. The first solut ion t hat springs t o m ind ( which is also visible in t he t est I wrot e previously) would be t o also j ust creat e an em pt y Customer class. Aft er all, I will need it really soon, and I 'm not going t o int eract wit h it in t his t est , so t here isn't a lot of coupling added bet ween t he building bricksat least not yet . On t he ot her hand, it feels like we are t ouching on t oo m any t hings in t his first lit t le t est . That 's a t ypical sm ell, so let 's t ry t o avoid t hat if possible. The second solut ion I t hought about would be t o m ock t he Customer and t hereby creat e looser coupling bet ween t he Aggregat es, at least in t his t est . I cert ainly do like t he idea of m ocks, but it feels a lit t le bit like overkill here. Aft er all, I 'm not going t o int eract wit h t he Customer at all; I j ust need t he Customer m ore or less as a placeholder. The t hird solut ion could be t o creat e an int erface called ICustomer ( or som et hing sim ilar, or let Customer be an int erface) and t hen creat e a st ub im plem ent at ion of t he int erface. Again, t hat feels like overkill right now, and what would t he purpose be when m oving forward? To be able t o swap ICustomer im plem ent at ions? I s t hat really som et hing I expect ? No, I don't , so unt il proven wrong, I decide t hat t his isn't t he solut ion I am going t o st art wit h. The fourt h solut ion could be t o skip t he Customer param et er. I could do t hat t em porarily in order t o get going and t hen change t he t est when I add t he Customer param et er lat er on. I could also decide t hat I probably don't want gluing t he Order and t he Customer t oget her t o be a responsibilit y of t he fact ory. Could it have been a prem at ure design decision t hat TDD revealed in a few seconds? Not binding t he Order and Customer t oget her in t he fact ory would act ually add flexibilit y t o t he UI design so t hat I don't first have t o st art wit h choosing t he cust om er. I nst ead, I could st art by let t ing t he user fill in ot her inform at ion about t he new order and t hen ask t he user who t he cust om er is. On t he ot her hand, flexibilit y som ewhere usually m eans com plexit y som ewhere else. For exam ple, assum e prices are cust om er dependent ; what price should be used when I add OrderLines t o t he Order wit hout a defined Customer ? What should happen lat er? I t 's cert ainly not im possible t o solve, but we shouldn't int roduce com plexit y if we can avoid it . Anot her drawback wit h t he fourt h solut ion is t hat t he Order I get back from t he fact ory isn't really in a valid st at e. The rule was t hat every Order should have a Customer . Sure, t he order isn't persist ed in t he fact ory, but it 's st ill not in a valid st at e. The consum er m ust do som et hing t o t ransform t he order t o a valid st at e aft er t he fact ory has execut ed. A fift h solut ion could be t o use object as t he t ype for t he param et er t o CreateOrder() , but t hat 's not very purposeful, and it adds t o t he cast ing needs. The sam e goes if you invent som e generic int erface t hat all your Dom ain Model classes should im plem ent , like IEntity, and t hen use t hat as a param et er. What we are discussing now is using a sledgeham m er t o crack a nut . You probably t hink t hat I 'm violat ing t he principles of TDD here by t hinking t oo m uch inst ead of t rying it out wit h t est s, code, and refact oring. On t he ot her hand, during t he t im e it t akes you t o writ e t he nam e of t he t est , lot s of ideas and t hought s ( and decisions) will flee t hrough your m ind very fast and sem i- consciously. ( This, of course, depends on your experience wit h t he problem at hand.) Anyway, let 's not t hink t oo m uch and inst ead decide on one solut ion t o t ry out . I 'll grab t he first one. Adding an em pt y Customer class wit h a public default const ruct or is act ually very quick and easy t o do, so let 's do t hat . Now t he t est com piles, and I get a red bar because I only wrot e t he signat ure and only ret urned null from t he CreateOrder() m et hod in OrderFactory. I t 's t im e t o im plem ent CreateOrder() . The first t ry could look like t his:
//OrderFactory public static Order CreateOrder(Customer customer) { return new Order(); }
For t hat t o com pile, I m ust have an accessible default const ruct or of t he Order. I t could be public, but inst ead I decide t o let it be internal in order t o hinder t he consum er of t he Order class inst ant iat ing it except via t he OrderFactory. Sure, t his isn't t he perfect prot ect ion, but at least direct inst ant iat ion by m ist ake will only happen inside t he Dom ain Model, and t hat 's a st ep in t he right direct ion in m y opinion. Refact oring t im e? Well, what value does t he fact ory really add? Not anyt hing yet . I t j ust adds a bit of com plexit y. I really should have at least st art ed wit hout t he fact ory, because it shouldn't be around if it doesn't add value. Now I st ill t hink t here are quit e a lot of t hings it will deal wit h, such as snapshot t ing t he customer , adding null obj ect s, creat ing different kinds of orders, and a few ot her t hings. But t he code so far j ust feels silly now wit h a fact ory, don't you t hink? I t m ight add value lat er on, but now it 's less sim ple t han it could be. Let 's change it direct ly t o use t he sim plest possible const ruct ion for t his: direct use of a const ruct or.
N ot e I t feels st range t o refact or away from a fact ory. But it 's j ust a sign t hat I got carried away and st art ed out wit h a t oo det ailed design from t he beginning. Also wort h point ing out is t hat I had a CreateOrderLine() m et hod in t he sket ch in Chapt er 4, but life is sim pler wit hout it .
[Test] public void CanCreateOrder() { Order o = new Order(new Customer()); Assert.IsNotNull(o); }
Any ot her t est s t o writ e? Sure, what we have been discussing all along is how t o deal wit h t he customer param et er. So in m y opinion an int erest ing t est would be t o check t hat a newly creat ed order has a customer . I current ly don't have verificat ion of t hat . Let 's add a t est for doing so. [Test] public void CanCreateOrderWithCustomer() { Order o = new Order(new Customer()); Assert.IsNotNull(o.Customer); }
This t est t ells m e t hat I need t o add a Customer field or propert y on t he Order. ( I t hink it will be needed not only for m y t est , but also for som e of t he requirem ent s.) I n order t o cut down t he com plexit y, I decide on a read- only propert y like t his in t he Order class: //Order public readonly Customer Customer;
I f t here is no way t o change t he Customer of t he Order, we can expect it t o be ever- present ( and never- changing) , and t hat will sim plify t he ot her rules. I f you don't need t he flexibilit y... Now everyt hing com piles and life is good, and we do get a red bar. That 's easily fixed by m odifying t he const ruct or so it uses t he customer param et er. The const ruct or now looks like t his, and we get a green bar: //Order public Order (Customer customer) { Customer = customer; }
Some Domain Logic I n Figure 5- 2 , I m ent ioned t hat I should also add OrderDate t o t he Order. What could t he sem ant ics around t hat one be? The order should probably get an init ial OrderDate when first creat ed and t hen a final OrderDate when t he order get s t he st at us of Ordered ( or som et hing like t hat ) . Let 's express t hat in a t est : [Test] public void OrderDateIsCurrentAfterCreation() { DateTime theTimeBefore = DateTime.Now.AddMilliseconds(-1); Order o = new Order(new Customer()); Assert.IsTrue(o.OrderDate > theTimeBefore); Assert.IsTrue(o.OrderDate < DateTime.Now.AddMilliseconds(1)); }
The idea wit h t he t est is t o set up an int erval of spot s in t im e and t hen check t hat t he OrderDate is wit hin t his int erval. As usual, t his won't com pile. I need t o add a public OrderDate propert y. This t im e, I 'm using a privat e field + a public get t er since I 'm going t o change t he OrderDate value lat er on in t he lifecycle. And t his t im e, I let t he const ruct or of t he Order set t he OrderDate field wit hout adding anot her param et er t o t he const ruct or. For clarit y, here's t he piece: //Order private DateTime _orderDate; public Order (Customer customer) {
Customer = customer; _orderDate = DateTime.Now; } public DateTime OrderDate { get {return _orderDate;} }
We get a green bar. I st art ed t his sect ion wit h a diagram describing a subset of t he m odel in Figure 5- 2 . The m odel has evolved slight ly, as shown in Figure 5- 3 where I visualize t he code.
Figu r e 5 - 3 . Fir st pa r t t o im ple m e n t , Order, r e vise d
N ot e You m ight wonder why I draw figures by hand som et im es and wit h a UML t ool som et im es. The reason is t hat I want ed t o illust rat e t hat upfront , I j ust sket ch quickly as a help wit h t he t hought and com m unicat ion process. When I have im plem ent ed t he t hing, t hen I m ay visualize t he im plem ent at ion wit h a UML t ool.
When you com pare Figure 5- 3 wit h Figure 5- 2 , t he visible differences aren't t hat big. I t 's act ually j ust t hat I had t o im plem ent a first version of t he Customer class and t he relat ionship from Order t o Customer . That 's not a real change t o t he m odel; only t o t he subset diagram .
N ot e I also show t he const ruct or in Figure 5- 3 t o give a m ore det ailed view of how t he classes work.
I t 's t im e for refact oring before m oving on t o t he next st ep. First , I want ed t he call t o t he const ruct or of t he Order t o be m oved out t o t he [SetUp], but t hen I would have t o change t he last t est slight ly regarding t he t im e int erval, and t hat would m ake it a lit t le less t o t he point . Moreover, t he t hree t est s shown so far are j ust about t he creat ion, so I like having t he calls in t he t est m et hods t hem selves. Let 's leave refact oring for t he t im e being and m ove on wit h refining t he Dom ain Model.
Second Task: The OrderRepository + OrderNumber I n t he previous sect ion, we t alked about what t he OrderFactory could look like in order t o serve our need t o creat e new Order inst ances from scrat ch, but t hen decided t o skip t he fact ory for now. I n m y opinion, t he next problem nat urally is how t he OrderRepository should work out . I n Chapt er 4, I discussed GetOrders() of t he OrderRepository. Right now, I t hink t he m ost im port ant , t ypical m et hod is GetOrder(), and t herefore I 'd like t o st art wit h t hat one and t he ident ifier of t he Ent it y Order. While we're t alking about it , what is OrderNumber? Well, t ypically an order has som e form of unique ident ificat ion t hat could be used by hum ans for ident ifying one part icular order. One com m on solut ion is t o j ust assign a new num ber t o each order from an ever- increasing sequence of num bers. I n Figure 5- 4 , I show t he Order class again aft er m odificat ion and t he newly added m et hod in t he OrderRepository class.
Figu r e 5 - 4 . Order a n d OrderRepository
I know, I was supposed t o t alk about t he OrderRepository, but I t hink it is im port ant t o do som et hing about t he OrderNumber first . Do we have t o give t he Order it s ident it y when it is creat ed? I t hink it 's correct t o say t hat t he OrderNumber is 0 unt il t he order is saved ( m eaning t hat t he st at us is Ordered) for t he first t im e. Unt il it is, we don't want t o wast e an OrderNumber on som et hing t hat j ust m ight becom e a real order.
N ot e This depends very m uch on t he requirem ent s ( whet her it 's good or bad) , but I get a growing feeling t hat we are m ixing t wo different t hings here t he business ident ificat ion and t he persist ence ident ificat ion. We'll com e back t o t hat in a lat er chapt er. I also st rongly dislike t he coupling bet ween persist ence and business rules. I m ean t hat t he m om ent t he order is persist ed, it 's also " Ordered." We'll leave t hat for now and com e back t o it in Chapt er 7, " Let t he Rules Rule," when we focus on business rules.
So as usual we can t hen writ e a very sim ple t est like t his: [Test] public void OrderNumberIsZeroAfterCreation() {
Order o = new Order(new Customer()); Assert.AreEqual(0, o.OrderNumber); }
Guess what ? This won't com pile, so I add a new read- only propert y called OrderNumber t o t he Order class and let it use a privat e _orderNumber field. This way we dealt wit h OrderNumber from t he const ruct or perspect ive. I f t he OrderNumber propert y is read- only, however, how can we give it t he value when using t he OrderRepository for finding and reconst it ut ing an old Order? To m ake t he whole issue clear, let 's t ake a st ep back and writ e a t est and a [Setup] m et hod ( aft er having declared OrderRepository wit h t he nam e _repository) : [SetUp] public void SetUp() { _repository = new OrderRepository(); } [Test] public void OrderNumberCantBeZeroAfterReconstitution() { int theOrderNumber = 42; _FakeAnOrder(theOrderNumber); Order o = _repository.GetOrder(theOrderNumber)); Assert.AreEqual(theOrderNumber, o.OrderNumber); }
As usual, t he whole t hing won't com pile. I don't have an OrderRepository class, so I writ e one wit h j ust a signat ure for t he GetOrder() m et hod and no code. The t est code st ill won't com pile, so I have t o st ub t he _FakeAnOrder() in t he t est class. I get a red bar. I 'm j ust get t ing furt her and furt her away from t he problem I t hought I want ed t o focus on. To be able t o writ e t he _FakeAnOrder() m et hod, I need t o m ake it possible for t he Reposit ory t o know about an Order. I could go ahead and im plem ent a new m et hod in t he OrderRepository t hat is only t here for support ing ot her t est s, but I t hink t his is a good sign of m y needing t o writ e anot her t est inst ead. I need t o t est saving Orders, or at least m ake t he Reposit ory aware of Orders.
N ot e I have t o adm it t hat I have quit e a lot of ideas about how t he saving should really happen, but I 'll m ove on slowly here and writ e sim ple code t hat I will refact or a lot now. Please don't get upset about t his; it 's not t he final solut ion.
So, I add t he Ignore at t ribut e t o t he OrderNumberCantBeZeroAfterReconstitution() t est so t hat it won't give m e a red bar right now. Then I writ e anot her t est t hat looks like t his:
[Test] public void CanAddOrder() { _repository.AddOrder(new Order(new Customer())); }
As usual, it won't com pile. I need t o add t he AddOrder() m et hod t o t he OrderRepository. And as usual, I j ust add t he signat ure, but t his is not enough t o get a red bar. As a m at t er of fact , t here is no t est code at all in t he CanAddOrder() m et hod. The reason is t hat I 'm not keen on t he idea of let t ing t he Reposit ory publish any m et hods for t he sole purpose of an ext ernal t est class for checking t he inner st at e of t he Reposit ory. Sure, I could use t he GetOrder() m et hod, but t hen it 's t he chicken or t he egg scenario. The im plem ent at ion of t his m et hod is far from even being st art ed yet . I nst ead, I t ake a st ep back and add a privat e IList t o t he Reposit ory for holding on t o t he orders. I don't publish anyt hing about t he IList t o t he out side; it 's very m uch an im plem ent at ion det ail, and I already t hink I will have t o get rid of it really soon. I nst ead I use anot her assert ion, not a xUnit one, but an assert ion from t he Diagnostics nam espace for checking what I t hink should be checked. What t he Assert() does is check t hat t he st at em ent is t rue. I f not , t he developer will be not ified. The AddOrder() m et hod could look like t his: //OrderRepository public void AddOrder(Order order) { int theNumberOfOrdersBefore = _theOrders.Count; //TODO Add here... Trace.Assert(theNumberOfOrdersBefore == _theOrders.Count - 1); }
Need for Another Assert Lib? You should t hink t wice before using t he ordinary Diagnostics assert ions. One problem wit h t hat is t hat it won't int egrat e well wit h NUnit . Anot her problem is t hat I can't cust om ize it for how it should act in different sit uat ions like developm ent , cont inuous int egrat ion, bet a t est ing, and during product ion. I discussed t his som e m ore in m y earlier book [ Nilsson NED] . You m ight also wonder why I didn't m ake sure t hat t he t est could be expressed som ehow in a NUnit t est . I could expose t he necessary st at e t hat t he t est needs, but I prefer not t o if I don't have t o. I f I did, it would m ake it harder t o refact or t he Reposit ory class, andagainI 'm sure I need t o do t hat . The t echnique I used earlier is t o som e ext ent inspired by Design By Cont ract by Bert rand Meyer [ Meyer OOSC] . Even t hough it 's not form alized at all, t he assert ion expresses what t he AddOrder() ensures, t hat is it s assurances for post - condit ions. What could be t he pre- condit ions t hat t he AddOrder() requires? Perhaps t hat t he order isn't known t o t he Reposit ory before? No, I dislike seeing t hat as an error. That _theOrders isn't null? But t he m et hod would t hrow an except ion if it is. I t 's also t he case t hat t he whole t hing about _theOrders is a very t em porary solut ion, as you will soon see.
Let 's leave t his alone for now.
So let 's creat e Mytrace.Assert(), which will j ust t hrow an except ion if it receives false as t he param et er. That way, it at least int egrat es well wit h NUnit / Test driven.net . Com pile, t est , and red bar. Good. So let 's swap t he TODO- com m ent for t he add call: //OrderRepository public void AddOrder(Order order) { int theNumberOfOrdersBefore +1 = _theOrders.Count; _theOrders.Add(order); MyTrace.Assert(theNumberOfOrdersBefore == _theOrders.Count); }
And we now have a green bar. We are writ ing som e st range code here and t here, but we are m oving forward, and we are creat ing t est s t hat aren't weird at all along t he way, so let 's cont inue.
N ot e I could have also worked wit h a m ock t o verify t hat t he Syst em Under Test ( SUT) worked as expect ed, but you can probably envision how from Chapt er 3, " TDD and Refact oring," so let 's m ove forward.
Now it 's t im e t o go back t o t he OrderNumberCantBeZeroAfterReconstitution(), and what I st um bled on t he last t im e was t he help m et hod for t he t est t hat I called _FakeAnOrder(). I t could look like t his: //A test class public void _FakeAnOrder(int orderNumber) { Order o = new Order(new Customer()); _repository.AddOrder(o); }
Ouch, we are now experiencing anot her problem . Do you see it ? Yep, how can we get t he faked OrderNumber int o t he order inst ance? The OrderNumber propert y was read- only ( which m akes sense) , so using t hat one won't work. As a m at t er of fact , t his is a generic problem . I t can be expressed like t his: How can we from t he out side ( such as in a Reposit ory) set values in inst ances t hat are being reconst it ut ed from per sist ence?
Reconstituting an Entity from Persistence: How to Set Values from the
Outside I m ent ioned t he generic problem of set t ing values in an inst ance t hat is being recreat ed by reading it back from t he dat abase. I n t he case of OrderNumber it 's obviously a problem because t he OrderNumber will never change by let t ing t he consum er int eract wit h t he propert y direct ly, but it 's m ore or less t he sam e wit h ot her at t ribut es. Let 's for t he m om ent assum e t hat OrderDate is read/ writ e. I f t he consum er set s a new OrderDate, t here m ight need t o be som e checks kicking in. I t 's probably not int erest ing t o execut e t hese checks when t he OrderDate is get t ing a value at t he t im e an order is being read from t he dat abase and reconst it ut ed as an inst ance. There are several possible ways in which t o deal wit h t his problem . Let 's see what we can com e up wit h.
Use a Specific Constructor We could have a specific const ruct or t hat could be used j ust for t his reconst it ut ion. I t could look like t his: //Order public Order(int orderNumber, DateTime orderDate, Customer customer)
I t works, but it 's not very clear t o consum ers of t he Order t hat t hey aren't supposed t o use t hat const ruct or. Well, I could let it be int ernal, of course, t o lessen t he problem quit e a lot ! The int ent ion of t he const ruct or could becom e slight ly clearer if you use a st at ic nam ed m et hod as a fact ory like t his: //Order public static Order ReconstituteFromPersistence(int orderNumber , DateTime orderDate, Customer customer)
This not only m akes t he purpose of t he m et hod slight ly clearer, but also t he fact t hat t he consum er should not m ess wit h t his m et hod. St ill, it 's possible for t he consum er t o use t he m et hod by m ist ake, but againt hat could be dealt wit h by using an int ernal m et hod inst ead. I t 's also problem at ic when t he class get s m ore real world- ish, when inst ead t here are perhaps 50 propert ies t hat should be set at reconst it ut ion. I f t his is t he case, t he param et er list becam e unwieldy a long t im e ago.
Use a Specific Method, Typically in a Specific Interface Anot her opt ion is t o decide on a m et hod t hat t he Order class has t o im plem ent , and t ypically t his m et hod should j ust be reachable via a specific int erface. Consequent ly, t here is m uch less of a risk t hat t he consum er will m ess wit h t he m et hod by m ist ake. I f t he consum er want s t o do evil t hings, he can, but t hat 's m ore or less always a problem . I f it 's possible t o set values in t he inst ance from t he out side, t he consum er can do it t oo. That is act ually not necessarily a bad t hing. I nst ead you should t ake a react ive approach and decide t hat t he possible usage didn't creat e a problem . So what could t his look like? One approach could be t o have a m et hod like t his on t he Order: //Order public void SetFieldWhenReconstitutingFromPersistence
(int fieldKey, object value)
I t 's definit ely a bit m essy. Now we have t o set up a m ap of field keys, which m ust be m aint ained from now on, bot h as t he m ap and as regards t he SetFieldWhenReconstitutingFromPersistence() code it self. A sim ilar solut ion ( st ill wit hout a nice m aint ainabilit y st ory) would be t o swap t he fieldKey param et er for a fieldName param et er inst ead.
Use Reflection Against the Private Fields I could use reflect ion, but t here are pros and cons t o t his, as usual. The drawbacks are t hat reflect ion is slow ( at least com pared t o ordinary access, but is it t oo slow?) , we can't close down privileges for t he user, and we m ust know about t he int ernal st ruct ure such as field nam es from t he out side. ( The last t hing could be dealt wit h in part by adding privat e propert ies t hat are only t here t o be used for t he reflect ion or by set t ing up nam ing rules so t hat if t he propert y is called Name, t here should be a privat e field called _name. I t m ight becom e slight ly m ore robust , but t he basic problem is st ill t here.) The m ost im port ant advant age is t hat it 's not int rusive. You can creat e your Dom ain Model j ust t he way you want it , wit hout having t o add infrast ruct ure- specific and obscure const ruct ions.
N ot e Well, t here are sm all det ails, such as t hat Customer can't be readonly . We'll get back t o t his in dept h in lat er chapt ers.
A Totally Different Solution... We could go for a com plet ely different solut ion: for exam ple, keeping t he values out side t he inst ance all t he t im e in a " safe place" so t hat t he inst ance is j ust like a Proxy [ GoF Design Pat t erns] . I t really is a t ot ally different solut ion, along wit h it s ups and downs. However, I really don't like m aking such a big, specific decision right now. I t would also m ess up t he Dom ain Model classes wit h st uff t hat has not hing t o do wit h t he dom ain. Let 's cont inue as sim ply as possible. All t his has quit e a lot of infrast ruct ure flavor, hasn't it ? I 'd like t o post pone t he decision regarding how t o set values from t he out side unt il lat er, when I st art t hinking about what infrast ruct ure t o use for support ing m y Dom ain Model. On t he ot her hand, I need t o m ake som e sort of decision now in order t o m ove on wit h t he t est s. What 's t he sim plest m echanism t hat could possibly work for now? Unfort unat ely, t here is no really sim ple solut ion t hat I can com e up wit h now. That 's a good sign t hat I should probably go back and ret hink t he whole t hing.
N ot e I t 's pret t y ironic t hat we ended up in t his long discussion j ust because I st art ed t o work wit h t he sem ant ics around OrderNumber. But we are here now, so let 's end t he discussion.
For now, I decide t o use reflect ion against t he privat e fields. I writ e a helper m et hod in t he OrderRepository ( which probably should be fact ored out lat er) t hat looks like t his: //OrderRepository public static void SetFieldWhenReconstitutingFromPersistence (object instance, string fieldName, object newValue) { Type t = instance.GetType(); System.Reflection.FieldInfo f = t.GetField(fieldName , BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); f.SetValue(instance, newValue); }
At last , we can m ove on again. What a long discussion j ust because OrderNumber was read- only. Rem em ber, however, t hat t his was act ually a generic discussion about how t o let t he Reposit ories set values when reconst it ut ing inst ances from persist ence wit hout running int o problem s wit h read- only propert ies and code in set t ers. Do you rem em ber t he t est called OrderNumberCantBeZeroAfterReconstitution()? I t 's repeat ed here: [Test] public void OrderNumberCantBeZeroAfterReconstitution() { int theOrderNumber = 42; _FakeAnOrder(theOrderNumber); Order o = _repository.GetOrder(theOrderNumber); Assert.IsTrue(o.OrderNumber != 0); }
I added t he Ignore at t ribut e before so t hat t he t est wouldn't be execut ed. Now I t ake away t he Ignore at t ribut e, and I get a red bar. Tim e t o change t he _FakeAnOrder(). I m ake sure t hat t he new order get s t he OrderNumber of 42, like t his: //A test class public void _FakeAnOrder(int orderNumber) { Order o = new Order(new Customer()); OrderRepository.SetFieldWhenReconstitutingFromPersistence (o, "_orderNumber", orderNumber); _repository.AddOrder(o); }
St ill a red bar, but it 's due t o som et hing else t his t im e. I 'm afraid t hat I creat ed a sit uat ion for m yself
when t here were several reasons for red bars, which is not recom m ended. I t ook t oo big a leap in m y TDD approach. Anyway, t he problem is t hat I haven't im plem ent ed OrderRepository.GetOrder(). I t 's j ust an em pt y m et hod ( ret urning null) . For t he m om ent , we have t he orders in an IList so we can j ust it erat e over t he it em s in t he list , checking t hem one by one. I t could look like t his: //OrderRepository public Order GetOrder(int orderNumber) { foreach (Order o in _theOrders) { if (o.OrderNumber == orderNumber) return o; } return null; }
I know, it 's a naïve im plem ent at ion, but t hat 's j ust what I want right now. And guess what ? Green bars. So we are back on dry ground, but m y whole being is scream ing for refact oring t he SetFieldWhenReconstitutingFromPersistence() m et hod. The SetFieldWhenReconstitutingFromPersistence() m et hod j ust doesn't belong in t he OrderRepository. I creat e a RepositoryHelper class for now and m ove t he SetFieldWhenReconstitutingFromPersistence() m et hod t here. St ill a green bar aft er I changed t he call t o SetFieldWhenReconstitutingFromPersistence() . Let 's have a look at how t he Dom ain Model diagram looks now. You'll find t he revised diagram in Figure 5- 5 .
Figu r e 5 - 5 . Order a n d OrderRepository, r e vise d
I have t he growing feeling t hat t he whole t hing isn't as good as I want it , but I can't put m y finger on why. I 'm sure it will appear m ore clearly when I m ove on, so t hat 's what I 'd like t o do now. What 's next on t he list ? Perhaps fet ching t he list of orders for a cert ain cust om er.
Fetching a List of Orders So what we are t alking about is anot her m et hod in t he OrderRepository called GetOrders(), which t akes a cust om er as param et er for a st art . When we have found som e Orders, t he second part of t he problem is t o show som e values such as OrderNumber and OrderDate. I n Figure 5- 6 , you find a diagram showing what I t hink
Figu r e 5 - 6 . Order, Customer, a n d OrderRepository for list in g or de r s
we will be creat ing in t his sect ion.
N ot e Did you not ice how different t he m ult iplicit y in t he UML diagram in Figure 5- 6 is com pared t o how it would be expressed in a dat abase diagram ? I f you com e from a dat abase background, I t hink differences such as no navigable relat ionship from Customer t o Order m ight surprise you, and t he m ult iplicit y is one t o one for t he relat ionship t hat is t here because it is from Order t o Customer .
I t 's t im e for anot her t est . Again, I need t o fake Orders in t he OrderRepository for t he t est t o run. Here goes: [Test] public void CanFindOrdersViaCustomer() { Customer c = new Customer(); _FakeAnOrder(42, c, _repository); _FakeAnOrder(12, new Customer(), _repository); _FakeAnOrder(3, c, _repository); _FakeAnOrder(21, c, _repository); _FakeAnOrder(1, new Customer(), _repository); Assert.AreEqual(3, _repository.GetOrders(c).Count); }
As you saw, I changed _FakeAnOrder() so it now also t akes Customer and OrderRepository as param et ers. That 's a sim ple change, of course. Anot her pret t y sim ple t hing is needed for m aking t he solut ion com pile and t hat is adding t he GetOrders() m et hod. I t could look like t his ( aft er you first t ook t hat m andat ory t our of red, of course) : //OrderRepository public IList GetOrders(Customer customer) { IList theResult = new ArrayList(); foreach (Order o in _theOrders) { if (o.Customer.Equals(customer))
theResult.Add(o); } return theResult; }
I 'm not sure I really like t he previous code. I 'm t hinking about t he check for equalit y. Assum e t hat I have t wo Customer inst ances ( not t wo variables point ing t o t he sam e inst ance, but t wo separat e inst ances) , bot h wit h t he sam e CustomerNumber. Will t he call t o Equals() t hen ret urn t rue or false? I t depends on whet her Equals() has been overridden or not and whet her or not t he overridden im plem ent at ion uses t he CustomerNumber for deciding on equalit y.
N ot e Readers t hat are fam iliar wit h t he concept of t he I dent it y Map pat t ern [ Fowler PoEAA] and who see it as a m ust will probably wonder why we are having t his discussion at all. The idea wit h I dent it y Map is t hat you will not have t wo separat e inst ances for a single cust om er. The I dent it y Map will t ake care of t hat for you. The language it self, C# for exam ple, won't t ake care of t his for you. But when we discuss persist ence solut ions in Chapt er 8, " I nfrast ruct ure for Persist ence," and Chapt er 9, " Put t ing NHibernat e int o Act ion," we'll cover t his. Unt il t hen, we are on our own.
It's Time to Talk About Entities Well, we have already t ouched on t his subj ect several t im es in t his chapt er since Order and Customer are exam ples of Ent it ies, but let 's t ake a st ep back and focus on t he concept . I t is im port ant for us t o keep t rack of som e t hings in t he dom ain over t im e. No m at t er whet her a cust om er changes nam e or address, it 's st ill t he sam e cust om er, and it 's som et hing we are int erest ed in keeping t rack of. On t he ot her hand, if a cust om er's reference person changes, it is probably not anyt hing we care t o keep t rack of. A cust om er is a t ypical exam ple of an Ent it y [ Evans DDD] . Again, an Ent it y is som et hing we keep t rack of by ident it y rat her t han all it s values. To t ake an ext rem e exam ple of som et hing t hat isn't an Ent it y, let 's t hink about t he int eger 42. I don't care about t he ident it y of 42 at all; I only care about t he value. And when t he value changes, I no longer t hink it 's t he 42 t hat has changedit 's a t ot ally new value wit h no connect ion t o t he old one. Fort y- t wo is a Value Obj ect [ Evans DDD] and not an Ent it y. I f we t ake t hat ext rem e exam ple over t o our dom ain, we m ight be able t o say t hat it isn't int erest ing for us t o t rack ReferencePerson of a Customer by ident it y. I t 's only int erest ing for us t o t rack it by value. We'll get back t o Value Obj ect s lat er on in t his chapt er.
N ot e Of course, what should be t racked by ident it y and what should j ust be t hought of as values is highly dependent on t he dom ain. Let 's t ake an exam ple. As I said above, I believe t hat ReferencePerson in t his applicat ion can be dealt wit h as a Value Obj ect , but if t he
applicat ion is for, let 's say, a sales support applicat ion for salesm en, perhaps t hey see ReferencePerson as an Ent it y inst ead.
Back to the Flow Again Unfort unat ely, t he t est CanFindOrdersViaCustomer() won't execut e successfully aft er I override Equals. The reason is t hat all cust om ers used in t he t est have t he sam e CustomerNumber, t hat being zero, and t herefore I find five Orders t hat have t he right Customer , not t hree. We need t o change t he t est a lit t le bit . I creat e anot her lit t le helper m et hod called _FakeACustomer() like t his: //A test class private Customer _FakeACustomer(int customerNumber) { Customer c = new Customer(); RepositoryHelper.SetFieldWhenReconstitutingFromPersistence (c, "_customerNumber", customerNumber); return c; }
N ot e _FakeACustomer() should associat e t he cust om er wit h t he CustomerRepository ( or rat her t he
Unit of Work, when t hat com es in t o play) . Let 's skip t his for now because it won't affect t he flow now.
Then I change t he t est CanFindOrdersViaCustomer(), so I use _FakeACustomer() inst ead of j ust calling t he const ruct or of t he Customer direct ly. The code looks like t his aft er t he change. [Test] public void CanFindOrdersViaCustomer() { Customer c = _FakeACustomer(7); _FakeAnOrder(42, c, _repository); _FakeAnOrder(12, _FakeACustomer(1), _repository); _FakeAnOrder(3, c, _repository); _FakeAnOrder(21, c, _repository); _FakeAnOrder(1, _FakeACustomer(2), _repository); Assert.AreEqual(3, _repository.GetOrders(c).Count); }
We are back t o green bar again. I t hink it is t im e t o sum up where we are wit h a figure. Since Figure 5- 6 , we have also added
CustomerNumber t o t he Customer class, as you can see here in Figure 5- 7 .
Figu r e 5 - 7 . Order, Customer, a n d OrderRepository for list in g or de r s, r e vise d
The Bird's-Eye View What is it t hat we have t ried t o accom plish so far? To a guy like m e who has spent over a decade working wit h dat abases and dat abase- focused applicat ions, t he code looks pret t y st range when you t hink about it . For inst ance, t here are no j um ps t o a dat abase at all. Sure, it feels nice t o leave t hat for now when focusing on t he Dom ain Model and writ ing t est s, but I also want t o have t he possibilit y of writ ing t est s wit hout t ouching t he dat abase lat er on. What have t he t est s bought us t hus far? Well for st art ers, we've used t he t est s as a m eans t o discover and nail down t he behavior and specificat ion t hat we want t he Dom ain Model t o im plem ent . By st art ing out wit h Fake/ naïve im plem ent at ion, we've been able t o concent rat e our energy on invent ing t he API . We are also t rying hard t o com e up wit h, and playing wit h, t he Ubiquit ous Language. Finally, our t est s have now given us a solid foundat ion in which we can t ransit ion t he code from our naïve im plem ent at ion t o t he " real t hing." I also believe we have respect ed t he event ual t arget wit h t he design choices. We have also creat ed an abst ract ion layer in t he form of reposit ories so we can defer dealing wit h t he dat abase ( which is wort h deferring) . So, specifically what we have done was writ e a first fake version of t he OrderRepository...! We have also given t he OrderRepository several different responsibilit ies. That m ight be t he reason for m y uneasiness wit h t he design. I don't want t o focus on infrast ruct ure at all right nowI want t o post pone it unt il lat er on. However, because I not iced t hat what I 'm current ly working on is infrast ruct ure wit hin t he Dom ain Model, let 's wait wit h t he feat ure list for a while and consider som e refact oring t o get a nicer solut ion wit h t he faking of t he OrderRepository.
N ot e I 'm t rying t o use " st ub," " fake" and " m ock" wit h specific, explicit m eanings. Here's a quot e from Ast els [ Ast els TDD] : " St u bs: A class wit h m et hods t hat do not hing. They are sim ply t here t o allow t he syst em t o com pile and run. Fa k e s: A class wit h m et hods t hat ret urn a fixed value or values t hat can eit her be hardcoded or set program m at ically. M ock s: A class in which you can set expect at ions regarding what m et hods are called, wit h which param et ers, how oft en, et c. You can also set ret urn values for various calling sit uat ions. A m ock will also provide a way t o verify t hat t he expect at ions were m et ."
But t hat 's not t he only definit ion. I t 's discussed a lit t le different ly by Meszaros [ Meszaros XUnit ] : " A Te st St u b is an obj ect t hat is used by a t est t o replace a real com ponent on which t he SUT depends so t hat t he t est can cont rol t he indirect input s of t he SUT. This allows t he t est t o force t he SUT down pat hs it m ight not ot herwise exercise. " A Fa k e Obj e ct is an obj ect t hat replaces t he funct ionalit y of t he real depended- on com ponent in a t est for reasons ot her t han verificat ion of indirect input s and out put s. Typically, it will im plem ent t he sam e or a subset of t he funct ionalit y of t he real dependedon com ponent but in a m uch sim pler way. " A M ock Obj e ct is an obj ect t hat is used by a t est t o replace a real com ponent on which t he SUT depends so t hat t he t est can observe it s indirect out put s. Typically, t he Mock Obj ect fakes t he im plem ent at ion by eit her ret urning hard- coded result s or result s t hat were pre- loaded by t he t est ."
Faking the OrderRepository My first t hought was t hat I should fake som e of t he inner part s of OrderRepository so t hat t he sam e OrderRepository could be used in different cases and get an inst ance like a DataFetcher in t he const ruct or, which will do t he right t hing. When I t hink about it m ore, it feels a bit like overkill. More or less t he whole OrderRepository will have t o be swapped if I decide t o let t he OrderRepository t alk t o a relat ional dat abase inst ead of j ust being a fake. The whole class will have t o be swapped. Perhaps an int erface like t he one shown in Figure 5- 8 is what I need for now.
Figu r e 5 - 8 . IOrderRepository, fir st pr oposa l
I nst inct ively, I like t his a lot . I t m ight be because of m y COM herit age or because m any aut hors ( such as [ Szyperski Com ponent Soft ware] , [ Johnson J2EE Developm ent wit hout EJB] and [ Löwy Program m ing .NET Com ponent s] ) clearly st at e t hat program m ing against int erfaces is preferable t o program m ing against concret e classes. St illand I have already t ouched on t hisI do have concret e classes ( Customer and Order) wit hin t he signat ures of t he int erface. Again, I leave it like t his for now because I don't current ly see any value in creat ing int erfaces for t hose classes. Anot her t hing t hat com es t o m ind is t hat it m ight be nice t o creat e a m ore generic Reposit ory
int erface. I t could look like what is shown in Figure 5- 9 .
Figu r e 5 - 9 . IOrderRepository ( or r a t h e r IRepository) , se con d pr oposa l
This m ight grow on m e, but right now I prefer t he m ore specific int erface in Figure 5- 8 , as it clearly st at es exact ly what is needed. The generic IRepository requires m ore cast s when being used and m ight be incorrect ly t rying t o gat her all upcom ing Reposit ories in one and t he sam e shape. Anot her solut ion m ight be t o use generics in t he IRepository- int erface + a base class and t hereby at least get t he t ype safet y ( and avoid code duplicat ion) and avoid ext ra t ype cast s. One problem t hen is t hat I m ight add a Delete() m et hod t o t he Reposit ory, even t hough not all Reposit ories will need it . And even wit h generics, t he GetListByParent(parent) is t roublesom e because what if an Ent it y has several parent s? Well, it 's an early sket ch and t hat can be solved of course. But I can't help t hinking t hat it 's bet t er t o let t he code " speak as clearly as possible for now" and skip generalizat ions like t hat . I t 's j ust not feeling im port ant yet . As usual, no decisions are carved in st one. I can challenge t he decisions again lat er on when I can m ore easily see what pieces can be generalized, but for now, I 'll st ay wit h IOrderRepository. Because of t hat , I renam e t he OrderRepository class t o OrderRepositoryFake and let t he class im plem ent t he IOrderRepository. I also need t o change t he [SetUp] code in m y t est class. I t current ly looks like t his: //A test class, the [SetUp] _repository = new OrderRepository();
I t could now look like t his: //A test class, the [SetUp] _repository = new OrderRepositoryFake();
The declarat ion of _repository m ust use IOrderRepository as t he t ype. And t he t est s st ill work. I guess pure XPers would groan a lit t le bit at t he refact oring I j ust did. I act ually didn't have t o m ake t hat change now; it was a change for preparing for t he fut ure, when I need a real Reposit ory inst ead of t he faked one. OK, I can agree wit h t hat , but I can't help t hinking a lit t le bit about t he fut ure ( read: lat er chapt ers) when I will be hooking in infrast ruct ure.
I cross m y fingers t hat t his preparat ion will prove t o be good. I fell back int o old habit s of early guessing.... On t he ot her hand, we are of course free t o choose our own balance and don't have t o st ick t o XP dogm a!
A Few Words About Saving I m ent ioned saving dat a previously. AddOrder() isn't really saving; it j ust adds t he Order t o t he Reposit ory. Or should it m ean saving also? No, I don't t hink I want t hat behavior. What I want from AddOrder() is t hat t he Reposit ory ( and t he underlying infrast ruct ure) from t hat point should know about t he inst ance and deal wit h it when I ask for a PersistAll(). I need t o ask t he infrast ruct ure t o deal wit h t he save funct ionalit y. The quest ion for now is if I want t o st at e t he PersistAll() t o t he Reposit ory inst ance or t o som et hing at a higher level t hat m onit ors inst ances for all t he Reposit ories. Again, I t hink I 'm drift ing away from t he Dom ain Model and int o t he infrast ruct ure part . I 'm glad t he quest ion was raised, but part s of t he discussion will have t o wait unt il t he next chapt er. There is a great deal t hat we need t o discuss as far as t hat is concerned. For now, I 'm happy wit h AddOrder() as a way of let t ing t he Reposit ory know about t he inst ance. As far as t he t est s t hat have been writ t en so far are concerned, it doesn't really m at t er t hat t he inst ance is t ransient ( but prepared for being persist ed) and not persist ent at t hat point in t im e. OK, I 'm as eager as you are t o get writ ing som e m ore t est s. What 's on t he list ? Well, I have dealt wit h m ost of it in feat ure 2, " List t he orders when looking at a specific cust om er." What I haven't dealt wit h t here is showing t he t ot al am ount for each order, t ype of order, and reference person. Let 's t ake t hem one by one, st art ing wit h t ot al am ount for each order.
Total Amount for Each Order At first t his seem s like a t rivial t ask. I need t o creat e an OrderLine class t hat I hook int o t he Order class as a list . Then I can j ust it erat e over t he OrderLine collect ion and calculat e t he t ot al am ount . I could act ually do t hat from t he consum er code, of course, but I don't want t o. That would reveal t he algorit hm t o t he consum er, and it 's not all t hat clear or purposeful eit her. I n realit y, t he algorit hm will becom e m uch harder when we m ove furt her, adding discount s of m any different kinds, for exam ple. I nst ead, I want a read- only propert y called TotalAmount on t he Order class. Again, it could int ernally it erat e over t he OrderLine collect ion and calculat e t he value. But t his is get t ing t o be t oo m uch for m e, t he " prem at ure opt im izer guy" t hat I am ( even t hough I 'm t rying hard not t o be) . I nst ant iat ing all orderLines for each order for a customer j ust t o be able t o show t he TotalAmount of t he orders set s off t he alarm bells. This m ight not be a problem depending upon t he execut ion environm ent and chosen infrast ruct ure. On t he ot her hand, in t he case of a dist ribut ed syst em , it is a problem , and pot ent ially a big one t hat can hurt t he dat abase server, t he net work, and t he garbage collect or. I m ust adm it I have t o force m yself not t o deal wit h t his opt im izat ion right away. I t 's sim ple t o fix it direct ly, but it 's not really im port ant right now when I 'm crunching what t he Dom ain Model should look like. I t 's m uch m ore im port ant right now t o design for sim plicit y and clarit y, and get back lat er wit h a profiler t o see if it 's a real perform ance problem . So I st art wit h t he sim plest solut ion and can t hen refact or t he TotalAmount propert y lat er on if t he perform ance charact erist ics aren't good enough. Ah, I m ade it ! I skipped an opt im izat ion now. And it feels so good for t he m om ent . Let 's t ake a st ep back now t hat we have t hought a bit about t he TotalAmount. I t 's t im e t o writ e a t est ( bet t er lat e t han never) . I st art wit h t he sim plest possible one I can com e up wit h.
[Test] public void EmptyOrderHasZeroForTotalAmount() { Order o = new Order(new Customer()); Assert.AreEqual(0, o.TotalAmount); }
I t 's sim ple t o m ake t hat t est com pile. I j ust add a public read- only propert y called TotalAmount t o t he Order class and let it ret urn - 1. I t com piles and I get red. I change it so t hat t he propert y ret urns 0 and I have a green bar. I could leave t hat for now, but because I have t he cont ext set up for m e now, I cont inue a lit t le bit . Here's anot her t est : [Test] public void OrderWithLinesHasTotalAmount() { Order o = new Order(new Customer()); OrderLine ol = new OrderLine(new Product("Chair", 52.00)); ol.NoOfUnits = 2; o.AddOrderLine(ol); Assert.AreEqual(104.00, o.TotalAmount); }
Ouch, t here was a lot of design done in t hat single t est , and I now need t o creat e t wo classes and new m et hods at old classes t o j ust m ake it com pile. I t hink I 'd bet t er com m ent out t hat t est for t he m om ent and st art a lit t le m ore sim ply, focusing on t he OrderLine class. First of all, I want t he OrderLine t o get t he price from t he chosen product as a default . Let 's writ e a t est . [Test] public void OrderLineGetsDefaultPrice() { Product p = new Product("Chair", 52.00); OrderLine ol = new OrderLine(p); Assert.AreEqual(52.00, ol.Price); }
Even t hat is a pret t y large j um p t hat requires som e code t o be writ t en, but I 'm feeling confident right now, so let 's writ e a Product class. The second argum ent in t he const ruct or should be t he unit price. The Product class should also have t wo read- only propert ies: Description and UnitPrice. I also wrot e an OrderLine class wit h t wo m em bers: Product and Price. I t looks like t his: public class OrderLine {
public decimal Price = 0; private Product _product; public OrderLine(Product product) { _product = product; } public Product Product { get {return _product;} } }
The t est now com piles, but it 's red. I need t o change t he const ruct or so t hat I grab t he price from t he Product and put it int o t he OrderLine it self. The const ruct or now looks like t his: //OrderLine public OrderLine(Product product) { _product = product; Price = product.UnitPrice; }
We are back t o green again. I writ e a t est proving t hat I can override t he default price in t he OrderLine, but it 's not very int erest ing, so let 's leave it out here. The next t est I 'd like t o writ e is a calculat ion of t he TotalAmount of one orderLine. The t est could be like t his: [Test] public void OrderLineHasTotalAmount() { OrderLine ol = new OrderLine(new Product("Chair", 52.00)); ol.NumberOfUnits = 2; Assert.AreEqual(104.00, ol.TotalAmount); }
Two m ore design decisions t here. I need a NumberOfUnits field and a TotalAmount on t he OrderLine. I let t he NumberOfUnits be a public field, and I writ e t he TotalAmount as a read only propert y ret urning 0. I t com piles, but is red. I t hen change t he TotalAmount int o t his: //OrderLine public decimal TotalAmount { get {return Price * NumberOfUnits;} }
Green again. Let 's have a look at what we j ust did. I n Figure 5- 10, you find a diagram wit h OrderLine and Product.
Figu r e 5 - 1 0 . OrderLine a n d Product
I t 's t im e t o go back t o t he t est I com m ent ed out before. You'll find it again here, but I m ade it a lit t le bit " bigger" t his t im e, dealing wit h t wo OrderLines. [Test] public void OrderWithLinesHasTotalAmount() { Order o = new Order(new Customer()); OrderLine ol = new OrderLine(new Product("Chair", 52.00)); ol.NoOfUnits = 2; o.AddOrderLine(ol); OrderLine ol2 = new OrderLine(new Product("Desk", 115.00)); ol2.NoOfUnits = 3; o.AddOrderLine(ol2); Assert.AreEqual(104.00 + 345.00, o.TotalAmount); }
I n order t o get t his t o com pile, I need t o add AddOrderLine() and TotalAmount t o Order. I let t he read- only propert y TotalAmount j ust ret urn 0 for t he m om ent . And I set [Ignore] on OrderWithLinesHasTotalAmount() t est . I nst ead, I writ e anot her t est t o focus on AddOrderLine(). That t est could look like t his: [Test] public void CanAddOrderLine() { Order o = new Order(new Customer()); OrderLine ol = new OrderLine(new Product("Chair", 52.00)); o.AddOrderLine(ol); Assert.AreEqual(1, o.OrderLines.Count); }
For t his t o com pile, I need t o add bot h AddOrderLine() and an OrderLines propert y t o t he Order. Because t he t est will fail m eaningfully if eit her of t hem isn't working, I get st art ed by writ ing OrderLines t he way I t hink I want it , but don't finish AddOrderLine(). You can find t he code here: //Order
private IList _orderLines = new ArrayList(); public IList OrderLines { get {return ArrayList.ReadOnly(_orderLines);} } public void AddOrderLine(OrderLine orderLine) { }
I t com piles and is red. I add a line t o t he AddOrderLine() m et hod like t his: //Order public void AddOrderLine(OrderLine orderLine) { _orderLines.Add(orderLine); }
I t com piles and is green. I t 's t im e t o delet e t he Ignore at t ribut e from t he t est called OrderWithLinesHasTotal-Amount(). The t est is red because TotalAmount of t he Order j ust ret urns 0. I rewrit e t he TotalAmount propert y like t his: //Order public decimal TotalAmount { get { decimal theSum = 0; foreach (OrderLine ol in _orderLines) theSum += ol.TotalAmount; return theSum; } }
Sim ple, st raight forward, and green. And did you not ice t hat I resist ed t he urge t o opt im ize it ? I j ust wrot e it t he sim plest way I could t hink of, and I even alm ost did it wit hout groaning. Aft er all, I current ly see t he Order and it s OrderLines as t he Aggregat e and t herefore as t he default load unit as well.
Historic Customer Information I t hink it 's t im e for som e refact oring. Any sm ells? One t hing I didn't like m uch was t hat Order has a relat ionship wit h Customer . Sure, an Order has a Customer , t hat 's fine, but if I look at an Order one year lat er, I probably want t o see t he Customer inform at ion as it was when t he Order was creat ed, not as it is now. The ot her problem is t hat t he const ruct or t akes a Customer as a param et er. That m eans t hat t he
Order m ight get int o t he persist ent obj ect graph direct ly, wit hout a call t o IOrderRepository.AddOrder(), and t his m ight not be such a good idea. ( Well, it depends on your infrast ruct ure as well, but it 's not obvious t hat t here is a boundary.) AddOrder() is t he way of saying
t hat t his order should get persist ed when it 's t im e for saving. None of t hese problem s are at all hard t o deal wit h, but how do I com m unicat e t his clearly? I writ e a t est , t o t ry out a proposal: [Test] public void OrderHasSnapshotOfRealCustomer() { Customer c = new Customer(); c.Name = "Volvo"; Customer aHistoricCustomer = c.TakeSnapshot(); Order o = new Order(aHistoricCustomer); c.Name = "Saab"; Assert.AreEqual("Saab", c.Name); Assert.AreEqual("Volvo", o.Customer.Name); }
But I 'm not so sure about t his solut ion... Current ly, t he Customer is a pret t y sm all t ype, but it will probably grow a lot . Even t hough it 's sm all now, is it really int erest ing t o keep t rack of what reference people t he Customer had at t hat point in t im e? I t hink creat ing anot her t ype wit h only t he propert ies t hat are int erest ing m ight be a good solut ion here because it will also creat e an explicit boundary bet ween t he Aggregat es, but especially because t hat 's what t he underlying m odel indicat es. Let 's creat e a CustomerSnapshot ( som ewhat inspired by [ Fowler Snapshot ] regarding t he purpose, but different in im plem ent at ion) t hat only has t he m inim um am ount of propert ies, and let it be a value obj ect . The t est only has t o be t ransform ed very slight ly ( aft er I have correct ed t wo com pile errors: t he t ype for t he Customer propert y and t he param et er t o t he const ruct or) : [Test] public void OrderHasSnapshotOfRealCustomer() { Customer c = new Customer(); c.Name = "Volvo"; CustomerSnapshot aHistoricCustomer = c.TakeSnapshot(); Order o = new Order(aHistoricCustomer); c.Name = "Saab"; Assert.AreEqual("Saab", c.Name); Assert.AreEqual("Volvo", o.Customer.Name); }
Anot her t hing t o consider is whet her t he consum er or t he Order const ruct or is responsible for creat ing t he snapshot . Previously, I let t he consum er be responsible. Let 's change t hat so t hat t he const ruct or again t akes a Customer inst ance as t he param et er, as follows:
[Test] public void OrderHasSnapshotOfRealCustomer() { Customer c = new Customer(); c.Name = "Volvo"; Order o = new Order(c); c.Name = "Saab"; Assert.AreEqual("Saab", c.Name); Assert.AreEqual("Volvo", o.Customer.Name); }
And t he const ruct or of t he Order looks like t his: //Order public Order(Customer customer) { Customer = customer.TakeSnapshot(); }
Anot her t hing t o consider is if it 's t he correct place in t im e t o t ake t he snapshot when t he cust om er inst ance is creat ed. I sn't t hat t oo early? What if t he cust om er changes? Perhaps we should t ake t he snapshot at a t ransit ion for when t he cust om er accept s t he order? Lot s of int erest ing and im port ant quest ions, but I st art like t his now. Let 's j ust conclude t his sect ion wit h an updat e of t he Customer . See Figure 5- 11.
Figu r e 5 - 1 1 . An a dde d m e t h od for t a k in g a sn a psh ot of Customer
I t 's refact oring t im e again, I t hink. Hm m m ...I 'm not t ot ally happy wit h t he fact t hat I send a Customer inst ance t o GetOrders() when finding Orders. I t 's act ually j ust t he I D of t he cust om er t hat is im port ant for t he funct ionalit y, and it feels a bit st range t o use a current cust om er for fet ching orders wit h hist oric cust om er inform at ion. Anyway, I 'd like t o t hink about it som e m ore. Anot her quest ion is what we should call t he propert y of t he Order for seeing t he hist oric cust om er inform at ion of t hat order. Right now it 's called Order. Customer , but t hat says not hing about t he t im e aspect of t he Customer inform at ion. Perhaps Order.CustomerSnapshot is bet t er. I t hink it is, I m ake t hat change, and you can see t he new Order- class in Figure 5- 12.
Figu r e 5 - 1 2 . Ch a n ge d n a m e of Customer pr ope r t y
Yet anot her t hing is t hat Customers are inst ant iat ed direct ly and not wit h a CustomerFactory. I t 's absolut ely OK for now, I t hink. We add fact ories when needed, not before! Perhaps I should add t hat OrderFactory back again, add a m et hod t o it for CreateOrderLine(), and let it t ake bot h a Product and an Order as param et er ( or at least an Order) . On t he ot her hand, it feels good t o be able t o work wit h an OrderLine wit hout m aking it persist ent ( which will happen when I add t he order line t o a persist ent order) . Let 's see if t his m ight need t o be changed lat er on, but t his has raised anot her concern. I t hink I need t o discuss t he life cycle of an inst ance right here.
The Life Cycle of an Instance I n t he previous sect ion, I said t hat an order line becom es persist ent when I add it t o a persist ent order, and t his needs explaining. What I m ean is t hat an inst ance st art s it s life as t ransient when you do t he following, for exam ple: Product p = new Product();
Then t he Product is t ransient . I t is not fet ched from t he dat abase as it has never becom e persist ent . I f we want it t o becom e persist ent ( and st ored t o t he dat abase at next call t o PersistAll()) , we need t o ask a Reposit ory for help by calling AddProduct(). Depending upon t he infrast ruct ure t hat is used, t he Reposit ory m akes t he infrast ruct ure aware of t he product . Then again, when I ask t he Reposit ory for help wit h reconst it ut ing an inst ance from t he dat abase, t hat fet ched inst ance is persist ent when I get it . All changes I m ake t o it will be st ored at next PersistAll(). But what about OrderLine? I didn't ask t he Reposit ory t o AddOrderLine(), but I did ask t he Aggregat e root Order t o AddOrderLine(), and because of t hat t he order line is m ade persist ent , t oo. Wit hin Aggregat es, I t hink t he persist ence aspect should be cascaded. Let 's sum m arize what I t hink t he sem ant ics are for t he life cycle t hat I need in m y Dom ain Model. They are shown in Table 5- 1.
Ta ble 5 - 1 . Su m m a r y of t h e se m a n t ics for t h e life cycle of t h e D om a in M ode l in st a n ce s Ope r a t ion
Re su lt Re ga r din g Tr a n sie n t / Pe r sist e n t
Call t o new
Transient
Reposit ory.Add( inst ance) or persist ent I nst ance.Add( inst ance)
Persist ent in Dom ain Model
x.Persist All( )
Persist ent in Dat abase
Ope r a t ion
Re su lt Re ga r din g Tr a n sie n t / Pe r sist e n t
Reposit ory.Get ( )
Persist ent in Dom ain Model ( and Dat abase)
Reposit ory.Delet e( inst ance)
Transient ( and inst ance will get delet ed from dat abase at x.Persist All)
I n Figure 5- 13, you'll find t he life cycle described as a st at e diagram .
Figu r e 5 - 1 3 . Th e life cycle of a n in st a n ce
N ot e Evans t alks a lot about t his t oo... [ Evans DDD] . There is a splendid explanat ion for t his, so be sure t o read it . The sam e goes for Jordan/ Russell [ Jordan/ Russell JDO] . JDO has m ore com plex ( and flexible) sem ant ics, but of course I m ight discover lat er t hat t he sim ple sem ant ics I sket ched previously are not enough.
Don't m ix t his up wit h how t he infrast ruct ure ( for exam ple NHibernat e) sees t he t ransient / persist ent . What I t alk about here is t he way I want t he life cycle sem ant ics for m y Dom ain Model inst ances. I t 's t hen up t o t he infrast ruct ure t o help m e wit h t he want ed sem ant ics behind t he scenes. This will be discussed a lot m ore in lat er chapt ers. Talking about t he infrast ruct ure, t he hawk- eyed reader m ight wonder what I m eant wit h x.PersistAll() in Table 5- 1. I t ried t o sym bolize t hat unknown infrast ruct ure wit h x. Again, we can
forget about t hat for now. Back t o feat ure 2, list ing orders for a cust om er. The next sub- feat ure was t hat an order m ust have an ordert ype.
Type of Order A sim ple solut ion here would be t o add an OrderType inst ance t o each Order, as shown in Figure 5- 14.
Figu r e 5 - 1 4 . Order a n d OrderType
I considered whet her t he OrderType is really an Ent it y [ Evans DDD] , and I don't t hink it isit is a Value Obj ect [ Evans DDD] . The set of possible values are " global" for t he whole applicat ion and are very sm all and st at ic. I 'm probably on t he wrong t rack here. Sure, I could im plem ent OrderType as a Value Obj ect , but m uch is point ing in t he direct ion t hat an enum is good enough, and especially good enough for now, so I define an OrderType enum and add such a field t o t he Order.
Reference Person for an Order I t 's t im e t o t alk about ReferencePerson of an Order again. There I t hink we have an exam ple of a Value Obj ect [ Evans DDD] , alt hough it is a bit different t han t he OrderType ( which was a value obj ect candidat e) because reference people aren't global for t he whole syst em but rat her specific per Customer or Order. ( Well, t hat 's a good indicat ion t hat ReferencePerson shouldn't be an enum .) I n Figure 5- 15, you can see t he ReferencePerson " in act ion."
Figu r e 5 - 1 5 . Order a n d ReferencePerson
When we go for a Value Obj ect pat t ern [ Evans DDD] , it m ight feel obvious t hat we should use a st ruct in .NET inst ead of a class, but t here are recom m endat ions for size regarding when t o use a st ruct and when not t o, and you'll get m ore problem s wit h infrast ruct ure when it com es t o st ruct s. I t 's also t he case t hat you will see m ore boxing when using st ruct s, so t he decision isn't clear- cut . I 'm act ually leaning t o using classes, but m ost oft en I 'm m aking t hem im m ut able. I f you go t hat rout e, you need t o use all t he values of t he class for when you are overriding t he Equals() . This is what t he Equals() could look like for t he Value Obj ect ReferencePerson. ( Not e t hat I 'm com paring all t he fields of t he Value Obj ect , unlike how I did it wit h t he Ent it y before. Also not e t hat , I dent it y Map or not , overriding Equals() for Value Obj ect s is needed.) //ReferencePerson public override bool Equals(object o) { if (o == null) return false; if (this.GetType() != o.GetType()) return false; ReferencePerson other = (ReferencePerson) o;
if (! FirstName.Equals(other.FirstName)) return false; if (! LastName.Equals(other.LastName)) return false; return true; }
N ot e I ngem ar Lundberg com m ent ed on t he previous list ing wit h a m ore condensed version: //ReferencePerson.Equals() ReferencePerson other = o as ReferencePerson; return other != null && this.GetType() == other.GetType() && FirstName.Equals(other.FirstName) && LastName.Equals(other.LastName);
So we have now writ t en som e of t he core funct ionalit y in t he Dom ain Model. I n Figure 5- 16, you can see how t he developed Dom ain Model looks for t he m om ent .
Figu r e 5 - 1 6 . I m ple m e n t a t ion of t h e D om a in M ode l so fa r [View full size image]
OK, I t hink t hat will do for now and we need a break. The break will also m ean a change. When we get back t o refining t he Dom ain Model in lat er chapt ers, I won't use such a TDD- ish way of describing all t he t iny design decisions as we go. I t will be a m ore condensed descript ion. But let 's end t he chapt er wit h a discussion about anot her st yle of API .
Fluent Interface So far we have sket ched out a pret t y basic and prim it ive API for our Dom ain Model. Creat ing an order wit h t wo products could look like t his ( assum ing we are already holding on t o a specific customer ) : Order newOrder = new Order(customer); OrderLine orderLine; orderLine = new OrderLine(productRepository.GetProduct("ABC")); orderLine.NumberOfUnits = 42; newOrder.AddOrderLine(orderLine); orderLine = new OrderLine(productRepository.GetProduct("XYZ")); orderLine.NumberOfUnits = 3; newOrder.AddOrderLine(orderLine);
I t hink it 's pret t y easy t o follow. But it 's a bit verbose and not as fluent as it could be. A sim ple way of short ening it would be t o add numberOfUnits t o t he const ruct or of OrderLine, t aking it down t o t hree st at em ent s, like t his: Order newOrder = new Order(customer); newOrder.AddOrderLine(new OrderLine (42, productRepository.GetProduct("ABC"))); newOrder.AddOrderLine(new OrderLine (3, productRepository.GetProduct("XYZ")));
But it 's st ill not very fluent . Mart in Fowler's " Fluent I nt erface" [ Fowler Fluent I nt erface] is good inspirat ion. Let 's see if we can sket ch t he API t o be a bit m ore fluent . Perhaps som et hing like t his: Order newOrder = new Order(customer) .With(42, "ABC") .With(3, "XYZ");
I t 's bot h short er and clearer. I n m y sket ch, I kept t he first line because I t hink t hat is pret t y clear. Then t he With() m et hod t akes t he numberOfUnits as param et er and a product ident ificat ion so t hat With() int ernally can use t he productRepository for finding t he product. ( Let 's assum e for now t hat With() can find t he productRepository at a well known place.) With() also ret urns t he order, and t herefore we can chain several st at em ent s aft er each ot hers.
Let 's t ry out a variat ion. Assum e t hat we can't creat e t he whole order in one swoop, but want t o add an orderLine as t he result from a user act ion. Then we m ight prefer som et hing like t his for adding an orderLine:
newOrder.AddOrderLine("ABC").NumberOfUnits = 42;
This t im e AddOrderLine() ret urns t he orderLine and not t he order. We could t ake t hat furt her if we want t o be able t o set m ore values of t he orderLine by changing NumberOfUnits int o a m et hod inst ead t hat ret urns t he orderLine again, but you get t he point . The im port ant t hing t o t ake away from t his sect ion is how m uch bet t er your API can becom e if you play wit h it a bit .
Summary So we spent t he whole chapt er discussing som e init ial part s of t he m odel, but wit h a fairly det ailed discussion. As I see it , det ailed m odel discussions are best done wit h code and TDD inst ead of UML sket ches, and t his chapt er followed t hat idea. I t hink it 's a good idea t o sum m arize where we are in t he feat ure list before we leave t he chapt er. So far we have dealt wit h feat ures 2, 3, 7 and 9. Not t hat m uch, but clear progress. And t he progress is secured wit h quit e a lot of t est s. Good. We'll cont inue t he work wit h refining t he Dom ain Model in Chapt er 7. The focus will shift a bit in t he next chapt er. As you m ight have not iced, we sniffed on infrast ruct ure relat ed discussions several t im es in t his chapt er, so let 's t ake on t hose problem s next by preparing for adding infrast ruct ure.
Chapter 6. Preparing for Infrastructure I t 's t im e t o cont inue t he explorat ion of applying DDD t o our problem dom ain. There is no specific next st ep, but you m ay be wondering when I will st art dealing wit h t he infrast ruct ure. For inst ance, we haven't yet m ade it possible t o persist or depersist ( m at erialize or reconst it ut e) t he orders t o or from a dat abase. As a m at t er of fact , we haven't even t hought about , let alone decided on, using a dat abase. This is int ent ional. We want t o delay m aking t his decision a bit longer, at least if we are feeling safe and fam iliar wit h exist ing infrast ruct ural opt ions. I f t his is our first DDD- ish proj ect , t his m ight not be t he case, of course, but let 's pret end. The reason I want t o delay t he binding t o a relat ional dat abase is t hat in doing so we will focus on t he Dom ain Model wit h as few dist ract ions as possible. Hey, I 'm an old dat abase guy, so it 's OK for m e t o t alk about t he dat abase as a dist ract ion. I would never dare t o say t hat if I cam e from t he OO side. Seriously, I 'm not saying t hat t he dat abase int eract ion isn't im port ant ; on t he cont rary, I know t hat it is im port ant . But st aying away from t he dat abase a bit longer m akes t rying out and exploring bot h sm all and large m odel changes m ore product ive for us. I t 's also m uch easier t o writ e t he t est s, because changes done in t est s won't be persist ent bet ween execut ions. The t est execut ions t hem selves will also execut e m uch fast er, m aking it m uch m ore m anageable for t he developers t o run t he t est s aft er each code change. There are also problem s wit h t his approach. I have already said t hat we assum e we have good cont rol of dealing wit h t he probable upcom ing dat abase int eract ion, so t his is not where t he problem lies. I nst ead, t here's a good chance t hat you will want t o writ e som e UI prot ot ypes early on, not only for your own sake as a way of challenging your Dom ain Model, but also as a way t o dialogue wit h t he cust om er. I f t hese prot ot ypes have " live" dat a t hat t he user can int eract wit h, add t o, and so on, t hey will be m ore useful t han what is com m on for early prot ot ypes. As you m ight recall, we ended Chapt er 5, " A New Default Archit ect ure," wit h a prelim inary discussion about adding early UI exam ples. Users will find t hese exam ples even m ore int erest ing t o t ry out if t he dat a is also around aft er a rest art . ( Sure, t his can easily be done if you st art using t he planned persist ence solut ion direct ly. However, we expect t hat t his will increase t he overhead during early refact oring at t em pt s, so what we want is t o creat e an inexpensive " illusion." )
N ot e Wat ch out so t hat your cust om er ( or your boss) doesn't t hink t hat t he whole applicat ion is done aft er only having seen one or t wo prot ot ypes. Been t here, done t hat .
The second t hing I 'm aft er at t his point is t o be able t o writ e t est s for save scenarios here, again wit hout dealing wit h a real dat abase. You m ight ask what t he point of t his is. Again, I 'd like t o focus on t he m odel, t he sem ant ics " around" it , and so on.
I could cert ainly deal wit h t his problem on an individual basis in t he reposit ories, as I have done so far, but I see value in having consist ency and j ust applying one solut ion t o all t he reposit ories. I also want a solut ion t hat scales up t o early int egrat ion t est ing, and m ost im port ant ly I want t o writ e real reposit ories now and not st upid, t em porary code t hat should be t hrown away! So even t hough I st art ed out saying t hat it 's t oo early for infrast ruct ure, t his chapt er will deal wit h pr epar at ion for t he infrast ruct ure, and in such a way t hat we won't have t o redo work when we add infrast ruct ure. What we want is t o writ e infrast ruct ure- agnost ic code.
POCO as a Lifestyle What I also j ust said bet ween t he lines is t hat I 'd really like t o t ry t o keep t he m ain asset of m y applicat ions as free from infrast ruct ure- relat ed dist ract ions as possible. The Plain Old Java Obj ect ( POJO) and Plain Old CLR Obj ect ( POCO) m ovem ent st art ed out in Java land as a react ion against J2EE and it s huge im plicat ions on applicat ions, such as how it increased com plexit y in everyt hing and m ade TDD close t o im possible. Mart in Fowler, Rebecca Parsons, and Josh MacKenzie coined t he t erm POJO for describing a class t hat was free from t he " dum b" code t hat is only needed by t he execut ion environm ent . The classes should focus on t he business problem at hand. Not hing else should be in t he classes in t he Dom ain Model.
N ot e This m ovem ent is one of t he m ain inspirat ions for light weight cont ainers for Java, such as Spring [ Johnson J2EE Developm ent wit hout EJB] .
I n .NET land it has t aken a while for Plain Old... t o receive any at t ent ion, but it is now known as POCO. POCO is a som ewhat est ablished t erm , but it 's not very specific regarding persist ence- relat ed infrast ruct ure. When I discussed t his wit h Mart in Fowler he said t hat perhaps Persist ence I gnorance ( PI ) is a bet t er and clearer descript ion. I agree, so I 'll change t o t hat from now on in t his chapt er.
PI for Our Entities and Value Objects So let 's assum e we want t o use PI . What 's it all about ? Well, PI m eans clean, ordinary classes where you focus on t he business problem at hand wit hout adding st uff for infrast ruct ure- relat ed reasons. OK, t hat didn't say all t hat m uch. I t 's easier if we t ake a look at what PI is not. First , a sim ple lit m us t est is t o see if you have a reference t o any ext ernal infrast ruct ure- relat ed DLLs in your Dom ain Model. For exam ple, if you use NHibernat e as your O/ R Mapper and have a reference t o nhibernat e.dll, it 's a good sign t hat you have added code t o your Dom ain Model t hat isn't really core, but m ore of a dist ract ion. What are t hose dist ract ions? For inst ance, if you use a PI - based approach for persist ent obj ect s, t here's no r equir em ent t o do any of t he following: I nherit from a cert ain base class ( besides object) Only inst ant iat e via a provided fact ory Use specially provided dat at ypes, such as for collect ions I m plem ent a specific int erface
Provide specific const ruct ors Provide m andat ory specific fields Avoid cert ain const ruct s There is at least one m ore, and one t hat is so obvious t hat I forgot . You shouldn't have t o writ e dat abase code such as calls t o st ored procedures in your Dom ain Model classes. But t hat was so obvious t hat I didn't writ e specifically about it . Let 's t ake a closer look at each of t he ot her point s.
Inherit from a Certain Base Class Wit h fram eworks, a very com m on requirem ent for support ing persist ence is t hat t hey require you t o inherit from a cert ain base class provided by t he fram ework. The following m ight not look t oo bad: public class Customer : PersistentObjectBase { public string Name = string.Empty; ... public decimal CalculateDepth() ... }
Well, it wasn't t oo bad, but it did carry som e sem ant ics and m echanism s t hat aren't opt im al for you. For exam ple, you have used t he only inherit ance possibilit y you have for your Customer class because .NET only has single inherit ance. I t 's cert ainly arguable whet her t his is a big problem or not , t hough, because you can oft en design " around" it . I t 's a worse problem if you have developed a Dom ain Model and now you would like t o m ake it persist ent . The inherit ance requirem ent m ight very well require som e changes t o your Dom ain Model. I t 's pret t y m uch t he sam e when you st art developing a Dom ain Model wit h TDD. You have t he rest rict ion from t he beginning t hat you can't use inherit ance and have t o save t hat for t he persist ence requirem ent . Som et hing you should look out for is if t he inherit ance brings lot s of public funct ionalit y t o t he subclass, which m ight m ake t he consum er of t he subclass have t o wade t hrough m et hods t hat aren't int erest ing t o him . I t 's also t he case t hat it 's not usually as clean as t he previous exam ple, but m ost of t he t im e PersistentObjectBase forces you t o provide som e m et hod im plem ent at ions t o m et hods in PersistentObjectBase, as in t he Tem plat e Met hod pat t ern [ GoF Design Pat t erns] . OK, t his is st ill not a disast er, but it all adds up.
N ot e This doesn't necessarily have t o be a requirem ent , but can be seen as a convenience enabling you t o get m ost , if not all, of t he int erface im plem ent at ion t hat is required by t he
fram ework if t he fram ework is of t hat kind of st yle. We will discuss t his com m on requirem ent in a lat er sect ion. This is how it was done in t he Valhalla fram ework t hat Christ offer Skj oldborg and I developed. But t o be honest , in t hat case t here was so m uch work t hat was t aken care of by t he base class called EntityBase t hat im plem ent ing t he int erfaces wit h cust om code inst ead of inherit ing from EntityBase was really j ust a t heoret ical opt ion.
Only Instantiate via a Provided Factory Don't get m e wrong, I 'm not in any way against using fact ories. Nevert heless, I 'm not ecst at ic at being forced t o use t hem when it 's not m y own sound decision. This m eans, for inst ance, t hat inst ead of writ ing code like t his: Customer c = new Customer();
I h av e t o writ e code like t his: Customer c = (Customer)PersistentObjectFactory.CreateInstance (typeof(Customer));
N ot e I know, you t hink I did m y best t o be unfair by using ext rem ely long nam es, but t his isn't really any bet t er, is it ? Customer c = (Customer)POF.CI(typeof(Customer));
Again, it 's not a disast er, but it 's not opt im al in m ost cases. This code j ust looks a lot weirder t han t he first inst ant iat ion code, doesn't it ? And what oft en happens is t hat code like t his increases t est ing com plexit y. Oft en one of t he reasons for t he m andat ory use of a provided fact ory is t hat you will consequent ly get help wit h dirt y checking. So your Dom ain Model classes will get subclassed dynam ically, and in t he subclass, a dirt y- flag ( or several) is m aint ained in t he propert ies. The fact ory m akes t his t ransparent t o t he consum er so t hat it inst ant iat es t he subclass inst ead of t he class t he fact ory consum er asks for. Unfort unat ely, for t his t o work you will also have t o m ake your propert ies virt ual, and public fields can't be used ( t wo m ore sm all det ails t hat lessen t he PI - ness a lit t le) . ( Well, you can use public fields, but t hey can't be " overridden" in t he generat ed subclass, and t hat 's a problem if t he purpose of t he subclass is t o t ake care of dirt y t racking, for exam ple.)
N ot e
There are several different t echniques when using Aspect - Orient ed Program m ing ( AOP) in .NET, where runt im e subclassing t hat we j ust discussed is probably t he m ost com m only used. I 've always seen having t o declare your m em bers as virt ual for being able t o int ercept ( or advice) as a drawback, but Roger Johansson point ed som et hing out t o m e. Assum e you want t o m ake it im possible t o override a m em ber and t hereby avoid t he ext ra work and responsibilit y of support ing subclassing. Then t hat decision should affect bot h ordinary subclassing and subclassing t hat is used for reasons of AOP. And if you m ake t he m em ber virt ual, you are prepared for having it redefined, again bot h by ordinary subclassing and AOP- ish subclassing. I t m akes sense, doesn't it ?
Anot her com m on problem solved t his way is t he need for Lazy Load, but I 'd like t o use t hat as an exam ple for t he next sect ion.
Use "Specially" Provided Datatypes, Such as Collections I t 's not uncom m on t o have t o use special dat at ypes for t he collect ions in your Dom ain Model classes: special as in " not t hose you would have used if you could have chosen freely." The m ost com m on reason for t his requirem ent is probably for support ing Lazy Load, [ Fowler PoEAA] , or rat her im plicit Lazy Load, so t hat you don't have t o writ e code on your own for m aking it happen. ( Lazy Load m eans t hat dat a is fet ched j ust in t im e from t he dat abase.) But t he specific dat at ypes could also bring you ot her funct ionalit y, such as special delet e handling so t hat as soon as you delet e an inst ance from a collect ion t he inst ance will be regist ered wit h t he Unit of Work [ Fowler PoEAA] for delet ion as well. ( Unit of Work is used for keeping t rack of what act ions should be t aken against t he dat abase at t he end of t he current logical unit of work.)
N ot e Did you not ice t hat I said t hat t he specific dat at ypes could bring you funct ionalit y? Yep, I don't want t o sound overly negat ive about NPI ( Not - PI ) .
You could get help wit h bi- direct ionalit y so t hat you don't have t o code it on your own. This is yet anot her exam ple of som et hing an AOP solut ion can t ake care of for you.
Implement a Specific Interface Yet anot her very regular requirem ent on Dom ain Model classes for being persist able is t hat t hey im plem ent one or m ore infrast ruct ure- provided int erfaces. This is nat urally a sm aller problem if t here is very lit t le code you have t o writ e in order t o im plem ent t he int erface( s) and a bigger problem if t he opposit e is t rue. One exam ple of int erface- based funct ionalit y could be t o m ake it possible t o fill t he inst ance wit h values from t he dat abase wit hout hit t ing set t ers ( which m ight have specific code t hat you don't want
t o execut e during reconst it ut ion) . Anot her com m on exam ple is t o provide int erfaces for opt im ized access t o t he st at e in t he inst ances.
Provide Specific Constructors Yet anot her way of providing values t hat reconst it ut e inst ances from t he dat abase is by requiring specific const ruct ors, which are const ruct ors t hat have not hing at all t o do wit h t he business problem at hand. I t m ight also be t hat a default const ruct or is needed so t hat t he fram ework can inst ant iat e Dom ain Model classes easily as t he result of a Get operat ion against t he dat abase. Again, it 's not a very dram at ic problem , but a dist ract ion nonet heless.
Provide Mandatory Specific Fields Som e infrast ruct ure solut ions require your Dom ain Model classes t o provide specific fields, such as Guid- based Id- fields or int - based Version- fields. ( Wit h Guid- based Id- fields, I m ean t hat t he Id- fields are using Guids as t he dat at ype.) That sim plifies t he infrast ruct ure, but it m ight m ake your life as a
Dom ain Model- developer a bit harder. At least if it affect s your classes in a way you didn't want t o.
Avoid Certain Constructs/Forced Usage of Certain Constructs I have already m ent ioned t hat you m ight be forced t o use virt ual propert ies even if you don't really want t o. I t m ight also be t hat you have t o avoid cert ain const ruct s, and a t ypical exam ple of t his is read- only fields. Read- only ( as when t he keyword readonly is used) fields can't be set from t he out side ( except wit h const ruct ors) , som et hing t hat is needed t o creat e 100% PI - Dom ain Model classes. Using a privat e field t oget her wit h a get - only propert y is pret t y close t o a read- only field, but not exact ly t he sam e. I t could be argued t hat a read- only field is t he m ost int ent ion- revealing solut ion.
N ot e Som et hing t hat has been discussed a lot is whet her .NET at t ribut es are a good or bad t hing regarding decorat ing t he Dom ain Model wit h inform at ion about how t o persist t he Dom ain Model. My opinion is t hat such at t ribut es can be a good t hing and t hat t hey don't really decrease t he PI level if t hey are seen as default inform at ion t hat can be overridden. I t hink t he m ain problem is if t hey get t oo verbose t o dist ract t he reader of t he code.
PI or not PI? PI or not PI of course it 's not t ot ally binary. There are som e gray areas as well, but for now let 's be happy if we get a feeling for t he int ent ion of PI rat her t han how t o get t o 100% . Anyt hing ext rem e
incurs high cost s. We'll get back t o t his in Chapt er 9, " Put t ing NHibernat e int o Act ion," when we discuss an infrast ruct ure solut ion.
N ot e What is an exam ple of som et hing com plet ely binary in real life? Oh, one t hat I oft en rem ind m y wife about is when she says " t hat wom an was very pregnant ."
Som et hing we haven't t ouched on yet is t hat it also depends on at what point in " t im e" we evaluat e whet her we use PI or not .
Runtime Versus Compile Time PI So far I have t alked about PI in a t im eless cont ext , but it 's probably m ost im port ant at com pile t im e and not as im port ant at runt im e. " What does t hat m ean?" I hear you say? Well, assum e t hat code is creat ed for you, infrast ruct ure- relat ed code t hat you never have t o deal wit h or even see yourself. This solut ion is probably bet t er t han if you have t o m aint ain sim ilar code by hand. This whole subj ect is charged wit h feelings because it 's cont roversial t o execut e som et hing ot her t han what you wrot e yourself. The debugging experience m ight t urn int o a night m are!
N ot e Mark Burhop com m ent ed as follows: Hm m m ... This was t he original argum ent against C+ + from C program m ers in t he early 90s. " C+ + st icks in new code I didn't writ e." " C+ + hides what is really going on." I don't know t hat t his argum ent holds m uch wat er anym ore.
I t 's also harder t o inj ect code at t he byt e level for .NET classes com pared t o Java. I t 's not support ed by t he fram ework, so you're on your own, which m akes it a showst opper in m ost cases. What is m ost oft en done inst ead is t o use som e alt ernat ive t echniques, such as t hose I m ent ioned wit h runt im e- subclassing in com binat ion wit h a provided fact ory, but it 's not a big difference com pared t o inj ect ed code. Let 's sum m arize wit h calling it em it t ing code.
The Cost for PI Entitites/Value Objects I guess one possible react ion t o all t his is " PI seem s great why not use it all t he t im e?" I t 's a law of nat ure ( or at least soft ware) t hat when everyt hing seem s neat and clean and great and wit hout fault , t hen com e t he drawbacks. I n t his case, I t hink one such is overhead. I did m ent ion earlier in t his chapt er t hat speed is som et hing you will sacrifice for a high level of PI ness, at least for runt im e PI , because you are t hen direct ed t o use reflect ion, which is quit e
expensive. ( I f you t hink com pile- t im e PI is good enough, you don't need t o use reflect ion, but can go for an AOP solut ion inst ead and you can get a bet t er perform ance st ory.) You can easily prove wit h som e operat ion in a t ight loop t hat it is m agnit udes slower for reading from / writ ing t o fields/ propert ies wit h reflect ion com pared t o calling t hem in t he ordinary way. Yet , is t he cost t oo high? I t obviously depends on t he sit uat ion. You'll have t o run t est s t o see how it applies t o your own case. Don't forget t hat a j um p t o t he dat abase is very expensive com pared t o a lot you're doing in your Dom ain Model, yet at t he sam e t im e, you aren't com paring apples and apples here. For inst ance, t he com parison m ight not be bet ween an ordinary read and a reflect ion- based read.
A Typical Example Regarding Speed Let 's t ake an exam ple t o give you a bet t er underst anding of t he whole t hing. One com m on operat ion in a persist ence fram ework is deciding whet her or not an inst ance should be st ored t o t he dat abase at t he end of a scenario. A com m on solut ion t o t his is t o let t he inst ance be responsible for signaling IsDirty if it is t o be st ored. Or bet t er st ill, t he inst ance could also signal it self t o t he Unit of Work when it get s dirt y so t hat t he Unit of Work will rem em ber t hat when it 's t im e t o st ore changes. But ( you know t here had t o be a " but ," right ?) t hat requires som e abuse of PI , unless you have paid wit h AOP.
N ot e There are ot her drawbacks wit h t his solut ion, such as it won't not ice t he change if it 's done via reflect ion and t herefore t he inst ance changes won't get st ored. This drawback was a bit t wist ed, t hough.
An alt ernat ive solut ion is not t o signal anyt hing at all, but let t he infrast ruct ure rem em ber how t he inst ances looked when fet ched from t he dat abase. Then at st ore t im e com pare how t he inst ances look now t o how t hey looked when read from t he dat abase. Do you see t hat it 's not j ust a com parison of one ordinary read t o one reflect ion- based read, but t hey are t ot ally different approaches, wit h t ot ally different perform ance charact erist ics? To get a real feeling for it , you can set up a com parison yourself. Fet ch one m illion inst ances from t he dat abase, m odify one inst ance, and t hen m easure t he t im e difference for t he st ore operat ion in bot h cases. I know, it was anot her t wist ed sit uat ion, but st ill som et hing t o t hink about .
Other Examples That was som et hing about t he speed cost , but t hat 's not all t here is t o it . Anot her cost I point ed out before was t hat you m ight get less funct ionalit y aut om at ically if you t ry hard t o use a high level of PI . I 've already gone t hrough m any possible feat ures you could get for free if you abandon som e PI ness, such as aut om at ic bi- direct ional support and aut om at ic im plicit Lazy Load. I t 's also t he case t hat t he dirt y t racking isn't j ust about perform ance. The consum er m ight be very int erest ed as well in using t hat inform at ion when paint ing t he form sfor exam ple, t o know what but t ons t o enable.
So as usual, t here's a t radeoff. I n t he case of PI versus non- PI , t he t radeoff is overhead and less funct ionalit y versus dist ract ing code in t he core of your applicat ion t hat couples you t o a cert ain infrast ruct ure and also m akes it harder t o do TDD. There are pros and cons. That 's reasonable, isn't it ?
The Cost Conclusion So t he conclusion t o all t his is t o be aware of t he t radeoffs and choose carefully. For inst ance, if you get som et hing you need alongside a drawback you can live wit h, don't be t oo religious about it ! That said, I 'm current ly in t he pro- PI cam p, m ost ly because of how nice it is for TDD and how clean and clear I can get m y Ent it ies and Value Obj ect s. I also t hink t here's a huge difference when it com es t o your preferred approach. I f you like st art ing from code, you'll probably like PI a great deal. I f you work in an int egrat ed t ool where you st art wit h det ailed design in UML, for exam ple, and from t here generat e your Dom ain Model, PI is probably not t hat im port ant for you at all. But t here's m ore t o t he Dom ain Model t han Ent it ies and Value Obj ect s. What I 'm t hinking about are t he Reposit ories. St rangely enough, very lit t le has been said as far as PI for t he Reposit ories goes.
PI for Our Repositories I adm it it : saying you use PI for Reposit ories as well is pushing it . This is because t he purpose of Reposit ories is pret t y m uch t o give t he consum er t he illusion t hat t he com plet e set of Dom ain Model inst ances is around, as long as you adhere t o t he prot ocol t o go t o t he Reposit ory t o get t he inst ances. The illusion is achieved by t he Reposit ories t alking t o infrast ruct ure in specific sit uat ions, and t alking t o infrast ruct ure is not a very PI - ish t hing t o do. For exam ple, t he Reposit ories need som et hing t o pull in order t o get t he infrast ruct ure t o work. This m eans t hat t he assem bly wit h t he Reposit ories needs a reference t o an infrast ruct ure DLL. And t his in it s t urn m eans t hat you have t o choose bet ween whet her you want t he Reposit ories in a separat e DLL, separat e from t he Dom ain Model, or whet her you want t he Dom ain Model t o reference an infrast ruct ure DLL ( but we will discuss a solut ion soon t hat will give you flexibilit y regarding t his) .
Problems Testing Repositories I t 's also t he case t hat when you want t o t est your Reposit ories, t hey are connect ed t o t he O/ R Mapper and t he dat abase.
N ot e Let 's for t he m om ent assum e t hat we will use an O/ R Mapper. We'll get back t o a m ore t horough discussion about different opt ions wit hin a few chapt ers.
Suddenly t his provides you wit h a pret t y t ough t est ing experience com pared t o when you t est t he Ent it ies and Value Obj ect s in isolat ion.
Of course, what you could do is m ock your O/ R Mapper. I haven't done t hat m yself, but it feels a bit bad on t he " bang for t he bucks" rat ing. I t 's probably quit e a lot of work com pared t o t he ret urn.
Problems Doing Small Scale Integration Testing I n previous chapt ers I haven't really shown any t est code t hat focused on t he Reposit ories at all. Most of t he int erest ing t est s should use t he Dom ain Model. I f not , it m ight be a sign t hat your Dom ain Model isn't as rich as it should be if you are going t o get t he m ost out of it . That said, I did use Reposit ories in som e t est s, but really m ore as sm all int egrat ion t est s t o see t hat t he cooperat ion bet ween t he consum er, t he Ent it ies, and t he Reposit ories worked out as planned. As a m at t er of fact , t hat 's one of t he advant ages Reposit ories have com pared t o ot her approaches for giving persist ence capabilit ies t o Dom ain Models, because it was easy t o writ e Fake versions of t he Reposit ories. The problem was t hat I wrot e quit e a lot of dum b code t hat has t o be t ossed away lat er on, or at least rewrit t en in anot her assem bly where t he Reposit ories aren't j ust Fake versions. What also happened was t hat t he sem ant ics I got from t he Fake versions wasn't really " correct ." For inst ance, don't you t hink t he following seem s st range? [Test] public void FakeRepositoryHaveIncorrectSemantics() { OrderRepository r1 = new OrderRepository(); OrderRepository r2 = new OrderRepository(); Order o = new Order(); r1.Add(o); x.PersistAll(); //This is fine: Assert.IsNotNull(r1.GetOrder(o.Id)); //This is unexpected I think: Assert.IsNull(r2.GetOrder(o.Id)); }
N ot e As t he hawk- eyed reader saw, I decided t o change AddOrder() t o Add() since t he last chapt er.
I 'm get t ing a bit ahead of m yself in t he previous code because we are going t o discuss save scenarios short ly. Anyway, what I want ed t o show was t hat t he Fake versions of Reposit ories used so far don't work as expect ed. Even t hough I t hought I had m ade all changes so far persist ent wit h PersistAll(), only t he first Reposit ory inst ance could find t he order, not t he second Reposit ory inst ance. You m ight wonder why I would like t o writ e code like t hat , and it 's a good quest ion, but it 's a pret t y big m isbehavior in m y opinion. What we could do inst ead is m ock each of t he Reposit ories, t o t est out t he cooperat ion wit h t he Ent it ies, Reposit ories, and consum er. This is pret t y cheaply done, and it 's also a good way of t est ing
out t he consum er and t he Ent it ies. However, t he t est value for t he Reposit ories t hem selves isn't big, of course. We are kind of back t o square one again, because what we want t hen is t o m ock out one st ep furt her, t he O/ R Mapper ( if t hat 's what is used for dealing wit h persist ence) , and we have already t alked about t hat .
Earlier Approach So it 's good t o have Reposit ories in t he first place, especially when it com es t o t est abilit y. Therefore I used t o swallow t he bit t er pill and deal wit h t his problem by creat ing an int erface for each Reposit ory and t hen creat ing t wo im plem ent ing classes, one for Fake and one for real infrast ruct ure. I t could look like t his. First , an int erface in t he Dom ain Model assem bly: public interface ICustomerRepository { Customer GetById(int id); IList GetByNamePattern(string namePattern); void Add(Customer c); }
Then t wo classes ( for exam ple, FakeCustomerRepository and MyInfrastructureCustomer-Repository) will be locat ed in t wo different assem blies ( but all in one nam espace, t hat of t he Dom ain Model, unless of course t here are several part it ions of t he Dom ain Model) . See Figure 6- 1 .
Figu r e 6 - 1 . Tw o Re posit or y a sse m blie s
That m eans t hat t he Dom ain Model it self won't be affect ed by t he chosen infrast ruct ure when it com es t o t he Reposit ories, which is nice if it doesn't cost anyt hing. But it does cost . I t also m eans t hat I have t o writ e t wo Reposit ories for each Aggregat e root , and wit h t ot ally different Reposit ory code in each case.
Furt her on, it m eans t hat t he product ion version of t he Reposit ories lives in anot her assem bly ( and so do t he Fake Reposit ories) , even t hough I t hink Reposit ories are part of t he Dom ain Model it self. " Two ext ra assem blies," you say, " That 's no big deal." But for a large applicat ion where t he Dom ain Model is part it ioned int o several different assem blies, you'll learn t hat t ypically it doesn't m ean t wo ext ra assem blies for t he Reposit ories, but rat her t he am ount of Dom ain Model assem blies m ult iplied by t hree. That is because each Dom ain Model assem bly will have it s own Reposit ory assem blies. Even t hough I t hink it 's a negat ive aspect , it 's not nearly as bad as m y having t he silly code in t he Fake versions of t he Reposit ories. That feels j ust bad.
A Better Solution? The solut ion I decided t o t ry out was creat ing an abst ract ion layer t hat I call NWorkspace [ Nilsson NWorkspace] . I t 's a set of adapt er int erfaces, which I have writ t en im plem ent at ions for in t he form of a Fake. The Fake is j ust t wo levels of hasht ables, one set of hasht ables for t he persist ent Ent it ies ( sim ulat ing a dat abase) and one set of hasht ables for t he Unit of Work and t he I dent it y Map. ( The I dent it y Map keeps t rack of what ident it ies, t ypically prim ary keys, are current ly loaded.) The ot her im plem ent at ion I have writ t en is for a specific O/ R Mapper.
N ot e When I use t he nam e NWorkspace from now on, you should t hink about it as a " persist ence abst ract ion layer." NWorkspace is j ust an exam ple and not im port ant in it self.
Thanks t o t hat abst ract ion layer, I can m ove t he Reposit ories back t o t he Dom ain Model, and I only need one Reposit ory im plem ent at ion per Aggregat e root . The sam e Reposit ory can work bot h against an O/ R Mapper and against a Fake t hat won't persist t o a dat abase but only hold in m em ory hasht ables of t he inst ances, but wit h sim ilar sem ant ics as in t he O/ R Mapper- case. See Figure 6- 2 .
Figu r e 6 - 2 . A sin gle se t of Re posit or ie s t h a n k s t o a n a bst r a ct ion la ye r
The Fake can also be serialized t o/ deserialized from files, which is great for creat ing very com pet ent , realist ic, and at t he sam e t im e ext rem ely refact oring- friendly early versions of your applicat ions. Anot her possibilit y t hat suddenly feels like it could be achieved easily ( for a sm all abst ract ion layer API at least ) could be t o Mock t he infrast ruct ure inst ead of each of t he Reposit ories. As a m at t er of fact , it won't be a m at t er of Mocking one infrast ruct ure- product , but all infrast ruct ure product s t hat at one t im e will have adapt er im plem ent at ions for t he abst ract ion layer ( if t hat happens, t hat t here will be ot her im plem ent at ions t han t hose t wo I wrot eit 's probably not t hat likely) . So m ore t o t he point , what is t hen being Mocked is t he abst ract ion layer. I t 's st ill a st ret ch t o t alk about PI Reposit ories, but wit h t his solut ion I can avoid a reference t o t he infrast ruct ure in t he Dom ain Model. That said, in real- world applicat ions I have kept t he Reposit ories in a separat e assem bly anyway. I t hink it clarifies t he coupling, and it also m akes som e hacks easier t o achieve and t hen let t ing som e Reposit ory m et hods use raw SQL where t hat proves necessary ( by using connect ion st rings as m arkers for whet her opt im ized code should be used or not ) . However, inst ead of referring t o t he Persist ence Fram ework, I have t o refer t o t he NWorkspace DLL wit h t he adapt er int erfaces, but t hat seem s t o be a big st ep in t he right direct ion. I t 's also t he case t hat t here are lit t le or no dist ract ions in t he Reposit ories; t hey are pret t y " direct " ( t hat is, if you find t he NWorkspace API in any way decent ) . So inst ead of writ ing a set of Reposit ories wit h code against an infrast ruct ure vendor's API and anot her set of Reposit ories wit h dum m y code, you writ e one set of Reposit ories against a ( naïve) at t em pt for a st andard API .
N ot e I 'm sorry for nagging, but I m ust say it again: I t 's t he concept I 'm aft er! My own im plem ent at ion isn't im port ant at all.
Let 's find anot her t erm for describing t hose Reposit ories inst ead of calling t he PI Reposit ories. What about single- set Reposit ories? OK, we have a t erm for now for describing when we build a single set of Reposit ories t hat can be used bot h in Fake scenarios and in scenarios wit h a dat abase. What 's probably m ore int erest ing t han nam ing t hose Reposit ories is seeing t hem in act ion.
Some Code in a Single-Set Repository To rem ind you what t he code in a Fake version of a Reposit ory could look like, here's a m et hod from Chapt er 5: //OrderRepository, a Fake version public Order GetOrder(int orderNumber) { foreach (Order o in _theOrders) { if (o.OrderNumber == orderNumber) return o; } return null; }
OK, t hat 's not especially com plex, but rat her silly, code. I f we assum e t hat t he OrderNumber is an I dent it y Field [ Fowler PoEAA] ( I dent it y Field m eans a field t hat binds t he row in t he dat abase t o t he inst ance in t he Dom ain Model) of t he Order, t he code could look like t his when we use t he abst ract ion layer ( _ws in t he following code is an inst ance of IWorkspace, which in it s t urn is t he m ain int erface of t he abst ract ion layer) : //OrderRepository, a single-set version public Order GetOrder(int orderNumber) { return (Order)_ws.GetById(typeof(Order), orderNumber); }
Pret t y sim ple and direct I t hink. Andagaint hat m et hod is done now, bot h for Fake and for when real infrast ruct ure is used!
The Cost for Single-Set Repositories So I have yet anot her abst ract ion. Phew, t here's get t ing t o be quit e a lot of t hem , don't you t hink? On t he ot her hand, I believe each of t hem adds value. St ill, t here's a cost , of course. The m ost obvious cost for t he added abst ract ion layer is probably t he t ranslat ion at runt im e t hat has t o be done for t he O/ R Mapper you're using. I n t heory, t he O/ R Mapper could have a nat ive im plem ent at ion of t he abst ract ion layer, but for t hat t o happen som e really popular such abst ract ion layer m ust be creat ed. Then t here's a cost for building t he abst ract ion layer and t he adapt er for your specific O/ R Mapper. That 's t he t ypical fram ework- relat ed problem . I t cost s a lot for building t he fram ework, but it can be used m any t im es, if t he fram ework ever becom es useful.
Wit h som e luck, t here will be an adapt er im plem ent at ion for t he infrast ruct ure you are using and t hen t he cost isn't yours, at least not t he fram ework- building cost . There's m ore, t hough. You have t o learn not only t he infrast ruct ure of your choice, but also t he abst ract ion layer, and t hat can't be neglect ed.
N ot e I t was easier in t he past as you only had t o know a lit t le about Cobol and files. Now you have t o be an expert on C# or Java, Relat ional Dat abases, SQL, O/ R Mappers, and so on, and so fort h. I f som eone t ries t o m ake t he whole t hing sim pler by adding yet anot her layer, t hat will t ip t he scales, especially for newcom ers.
Yet anot her cost is, of course, t hat t he abst ract ion layer will be kind of t he least com m on denom inat or. You won't find all t he power t here t hat you can find in your infrast ruct ure of choice. Sure, you can always bypass t he abst ract ion layer, but t hat com es wit h a cost of com plexit y and ext ernal Reposit ory code, and so on. So it 's im port ant t o invest igat e whet her your needs could be fulfilled wit h t he abst ract ion layer t o 30% , 60% , or 90% . I f it 's not a high percent age, it 's quest ionable whet her it 's int erest ing at all. Ok, let 's ret urn t o t he consum er for a while and focus on save funct ionalit y for a change.
Dealing with Save Scenarios As I said at t he end of t he previous chapt er, I will m ove fast er from now on and not discuss all t he st eps in m y t hought process. I nst ead it will be m ore like going direct ly t o t he decided solut ion. Not decided as in " done," but decided as in " for now." Wit h t hat said, I 'd st ill like t o discuss t est s, especially as a way of clarificat ion. Now I 'd like t o discuss save scenarios from t he consum er's point of view. So here's a t est for showing how t o save t wo new Customers: [Test] Public void CanSaveTwoCustomers() { int noOfCustomersBefore = _GetNumberOfStoredCustomers(); Customer c = new Customer(); c.Name = "Volvo"; _customerRepository.Add(c); Customer c2 = new Customer(); c2.Name = "Saab"; _customerRepository.Add(c2); Assert.AreEqual(noOfCustomersBefore, _GetNumberOfStoredCustomers()); _ws.PersistAll(); Assert.AreEqual(noOfCustomersBefore + 2, _GetNumberOfStoredCustomers()); }
At first glance, t he code j ust shown is pret t y sim ple, but it " hides" lot s of t hings we haven't discussed before. First , it reveals what kind of consum er code I want t o be able t o writ e. I want t o be able t o do a lot of st uff t o several different inst ances, and t hen persist all t he work wit h a single call such as PersistAll(). ( The call t o _GetNumberOfStoredCustomers() goes t o t he persist ence engine t o check t he num ber of persist ent cust om ers. I t 's not unt il aft er PersistAll() t hat t he num ber of persist ent cust om ers has increased.) A m issing piece of t he puzzle is t hat t he Reposit ory was fed wit h t he _ws at inst ant iat ion t im e. I n t his way, I can cont rol t he Reposit ories t hat should part icipat e in t he sam e Unit of Work and t hose t hat should be isolat ed in anot her Unit of Work. Yet anot her t hing t hat m ight be int erest ing is t hat I ask t he Reposit ories for help ( t he Add() call) in not ifying t he Unit of Work t hat t here is a new inst ance for persist ing at next PersistAll() call. I 'm referring t o t he life cycle I want t o have for a persist ent inst ance ( I t ouched on t his in Chapt er 5, so I won't repeat it here) .
What I t hink is wort h point ing out is t hat if I expect it t o be enough t o associat e t he Aggregat e root wit h t he Unit of Work, t he inst ances t hat are part of t he Aggregat e and t hat t he Aggregat e root reaches will get persist ed t o t he dat abase as well when we say PersistAll(). Again, Aggregat es assist us well; t hey provide a t ool for knowing t he size of graph t hat will be persist ed because t he Aggregat e root is m arked for being persist ed. Again, Aggregat es m ake for sim plificat ion.
N ot e O/ R Mappers are oft en able t o be configured for how far t he reach of persist by reachabilit y should go. But even when t hey are configured t hat way, t he Aggregat es are a very good guide in m y opinion. What I m ean is t hat I use t he Aggregat es for det erm ining how far t he reachabilit y should reach, when I do t he configurat ion.
Let 's t ake a closer look at t he reasoning behind t he decisions discussed so far.
Reasons for the Decisions Why did I choose as I did? First , I want t o use t he Unit of Work pat t ern. I want it s charact erist ics: t o creat e a logical Unit of Work. So you can m ake lot s of changes t o t he Dom ain Model, collect t he inform at ion in t he Unit of Work, and t hen ask t he Unit of Work t o save t he collect ed changes t o t he dat abase. There are several st yles of Unit of Work t o use. The one I prefer is t o m ake it as t ransparent as possible for t he consum er and t herefore t he only m essage needed is t o say Add() t o t he Reposit ory ( which in t urn will t alk t o t he Unit of Work) . I f t he reconst it ut ion is done via t he Reposit ory, t he Unit of Work- im plem ent at ion can inj ect som e obj ect t hat can collect inform at ion about changes. Ot herwise, t here can be a snapshot t aken at read t im e t hat will be used t o cont rol t he changes by t he Unit of Work at persist t im e. I also chose t o cont rol save or not save ( PersistAll()) out side of t he Reposit ories. I n t his part icular exam ple, I could j ust as well have had PersistAll() direct ly on t he CustomerRepository , but I chose not t o. Why? Why not let Reposit ories hide Unit of Work com plet ely? Well, I could, but I oft en find t hat I want t o synchronize changes t o several Aggregat es ( and t herefore also t o several different Reposit ories) in a single logical unit , and t hat 's t he reason. So code like t his is not only possible t o writ e, but also very t ypical: Customer c = new Customer(); _customerRepository.Add(c); Order o = new Order(); _orderRepository.Add(o); _ws.PersistAll();
One alt ernat ive m ight be t he following:
Customer c = new Customer(); _customerRepository.Add(c); _customerRepository.PersistAll(); Order o = new Order(); _orderRepository.Add(o); _orderRepository.PersistAll();
But t hen I have t wo different Unit of Work inst ances and t wo I dent it y Maps ( it doesn't h av e t o be t hat way, but let 's assum e it for t he sake of t he discussion) , which can give pret t y st range effect s if we aren't very careful. Aft er all, all five lines in t he first exam ple were one scenario, and because of t hat I find it m ost int uit ive and appropriat e t o t reat it like one scenario regarding how I deal wit h t he Unit of Work and I dent it y Map as well. I m ean, t he scenario should j ust have one Unit of Work and one I dent it y Map. Anot her t hing t hat m ight be a problem is t hat when t he Reposit ory hid t he Unit of Work it probably m eant t hat t here were t wo dat abase t ransact ions. That in t urn m eans t hat you m ight have t o prepare t o add com pensat ing operat ions when t he out com e of a scenario isn't as expect ed. I n t he previous case, it 's probably not t oo disast rous if t he Customer is added t o t he dat abase but not t he Order. However, it can be a problem , depending upon your Aggregat e design. That said, Aggregat es " should" be designed so t hat t hey are in a consist ent st at e at PersistAll() t im e. But t he loose relat ionship bet ween Aggregat es doesn't t ypically live under such st rict requirem ent s. That m ight m ake you like t he second solut ion. On t he ot her hand, t he second solut ion would st ore t wo t ot ally unrelat ed customers in t he sam e PersistAll() if bot h of t hose customers were associat ed t o t he Reposit ory. That is act ually less im port ant t han grouping a customer and it s orders t oget her. Aggregat es are about obj ect s, not classes. What speaks for t he solut ion in t he second exam ple is if one Aggregat e com es from one dat abase and t he ot her Aggregat e is st ored in anot her dat abase at anot her dat abase server. Then it 's probably easiest t o have t wo Unit of Work- inst ances anyway, one for each dat abase. So, solut ion t wo is slight ly less coupled.
N ot e I could even let Add() fulfill t he t ransact ion, but t hen I have different sem ant ics from t hose I have discussed and expressed so far. I t would be crucial t o call Add() at t he right point in t im e. This is less im port ant wit h t he solut ion I have chosen, as long as t he call is done before PersistAll(). Wit h Add() fulfilling t he t ransact ion, it would also m ean t hat it 's cert ainly not a m at t er of " gat her all changes and persist t hem all at PersistAll()," which again is very different from m y current solut ion. While we're at it , why not t hen encapsulat e t he whole t hing in t he Ent it y inst ead so t hat you can writ e t he following code? Customer c = new Customer() c.Name = "Saab"; c.Save();
I t hink it 's m oving away from t he st yle I like. I t hink it breaks t he Single Responsibilit y
Principle ( SRP) [ Mart in PPP] , and it 's low PI - level. I t hink I 'm also m oving int o " m at t er of t ast e" t errit ory.
I also have a problem wit h inconsist ency if one save goes well and ot hers do not . ( You could argue t hat physical t ransact ion and Unit of Work don't h av e t o be t he sam e, but t hat increases com plexit y in m y opinion. The way I see it is if you don't have t o do som et hing, you shouldn't .) However, by using m y favorit e t echnique, t here's not hing t o st op m e from get t ing t he sam e effect s of st oring one Aggregat e at a t im e if I really want t o by let t ing t he Reposit ories hide t he Unit of Work and I dent it y Map. I t could t hen look like t his: Customer c = new Customer(); _customerRepository.Add(c); _ws1.PersistAll(); Order o = new Order(); _orderRepository.Add(o); _ws2.PersistAll();
N ot e For t he previous scenario, t he end result would be t he sam e wit h a single _ws , but t hat depended on t he specific exam ple.
What I m ean is t hat I can have one Unit of Work/ I dent it y Map when I so wish, and several when I so wish. I t hink t his is slight ly m ore flexible, which I like a lot , and t his is one m ore reason for m y choice; nam ely t hat I current ly prefer t o see t he Unit of Work as som et hing belonging t o t he consum er of t he Dom ain Model ( t hat is t he Applicat ion layer or t he present at ion layer) rat her t han t he Dom ain Model it self. I f we assum e t hat each Reposit ory has it s own I dent it y Map, it can get a bit m essy if t he sam e order is referenced from t wo different Reposit ories, at least if you m ake changes t o t he sam e logical order ( but t wo different inst ances) in bot h Reposit ories. As far as risks are concerned, what it boils down t o is which risk you prefer. The risk of com m it t ing inst ances t hat you weren't done wit h because a PersistAll() call will deal wit h m ore inst ances t han you expect ed? Or t he risk of forget t ing t o com m it a change because you'll have t o rem em ber what Reposit ories t o ask t o do PersistAll(). I 'm not saying t hat it 's a solut ion wit hout problem s, but again, I prefer t he I dent it y Map and t he Unit of Work t o belong t o t he scenario.
N ot e Deciding on what program m ing m odel you want is up t o you, as usual. There are pros and cons t o each.
I have m ent ioned Unit of Work and I dent it y Map t oget her over and over again. I t 's such a com m on com binat ion, not only in m y t ext , but in product s as well. For exam ple, t here is Persist ence Manager in JDO [ Jordan/ Russell JDO] and Session in Hibernat e [ Bauer/ King HiA] . I t hought it m ight deserve a pat t ern, and I was t hinking about writ ing it up, but when I discussed it wit h Mart in Fowler he not ified m e t hat he discusses t hat in [ Fowler PoEAA] when he t alks about I dent it y Map and Unit of Work. That 's m ore t han enough, so I decided not t o repeat m ore about t hat . OK, now t here's been a lot of t alk and no act ion. Let 's st art building t he Fake m echanism and see where we end up.
Let's Build the Fake Mechanism Let 's m ove on in an int erface- based way for a while, or at least for a st art . I have already t ouched on a couple of t he m et hods, but let 's st art from t he beginning. A reduced version of t he int erface of t he abst ract ion layer ( which I earlier in t he chapt er already called IWorkspace) could look like t his: public interface IWorkspace { object GetById(Type typeToGet, object idValue); void MakePersistent(object o); void PersistAll(); }
So far t he whole int erface is pret t y st raight forward. The first m et hod is called GetById() and is used for reconst it ut ing an obj ect from t he dat abase. You say what t ype you expect and t he ident it y value of t he obj ect . The second m et hod, called MakePersistent(), is used for associat ing new inst ances wit h t he IWorkspace inst ance so t hat t hey will be persist ed at next PersistAll(). Finally, PersistAll() is for persist ing what is found in t he Unit of Work int o t he dat abase. MakePersistent() isn't needed if you have read t he inst ance from t he dat abase wit h GetById(),
because t hen t he inst ance is already associat ed wit h t he Unit of Work. So far I t hink you'll agree t hat t he API is ext rem ely sim ple, and I t hink it is very im port ant in order t o keep com plexit y down in t his abst ract ion layer. OK, it 's not all t hat com pet ent yet , so we need t o add m ore.
More Features of the Fake Mechanism The first t hing t hat springs t o m ind is t hat we need t o deal wit h t ransact ions as a very im port ant concept , at least from a correct ness st andpoint . On t he ot her hand, it 's not som et hing t hat is im port ant for m ost UI - program m ers. ( We could swap " UI - program m ers" for " UI - code" j ust as well.) What I m ean is t hat I don't want t o put t he responsibilit y for t ransact ion m anagem ent on t he UI program m er because it 's t oo m uch of a dist ract ion for him and t oo im port ant t o be seen as a dist ract ion. St ill, I want adapt able t ransact ion scope so t hat t here isn't only a predefined set of possible t ransact ions. So m y goals are pret t y sim ilar t o t hose of declarat ive t ransact ions in COM+ , but I have chosen a pret t y different API . I nst ead of set t ing at t ribut es on t he classes for describing whet her t hey require t ransact ions or not , I will j ust say t hat PersistAll() int ernally does all it s work in an explicit t ransact ion, even t hough you explicit ly didn't ask for it I know t hat on t he face of it t his feels overly sim ple t o m any old- t im ers. That goes for m e as well, because I believe t ransact ion handling is so im port ant t hat I like t o deal wit h it m anually. I f t he goal is t o be able t o deal wit h som et hing like 90% of t he sit uat ions, however, I t hink PersistAll() could very well use an explicit t ransact ion, and it 's as sim ple as t hat . Again, it sounds way t oo sim plist ic, and of course t here are problem s. One t ypical problem is logging.
Assum e t hat you log t o t he dat abase server; you don't always want t he logging operat ion t o fail if t he ordinary work fails. However, t hat 's sim ple t o deal wit h; you j ust use a separat e workspace inst ance for t he logging. I f you want it t o use t he abst ract ion layer at all, t he logging will probably j ust be im plem ent ed as a Service inst ead, which probably has not hing t o do wit h t he abst ract ion layer. As a m at t er of fact , t here's a good chance t hat you will use a t hird- part y product for logging, or perhaps som et hing like log4net [ Log4Net ] . I t 's not som et hing t hat will int erfere wit h or be dist urbed by t he t ransact ion API of t he abst ract ion layer. Anot her problem is t hat t here m ight well be a need for t he GetById() m et hod t o live in t he sam e t ransact ion as t he upcom ing PersistAll(). That won't happen by default , but if you want t o force t hat , you can call t he following m et hod before GetById(): void BeginReadTransaction(TransactionIsolationLevel til)
To em phasize t his even m ore, t here is also an overload t o GetById() t o ask for an exclusive lock, but t his com es wit h a warning t ag. Make sure you know what you're doing when you use t his! For exam ple, t here should be no user int eract ion what soever aft er BeginReadTransaction() or read wit h exclusive lock and before PersistAll(). But I digresswhat is im port ant for t he Fake? Because t he Fake only t arget s single- user scenarios, t he t ransact ional sem ant ics aren't very im port ant , and for reasons of sim plicit y t hose will probably not be dealt wit h at all. St ill, t he consum er code can be writ t en wit h t ransact ion- handling in m ind when t he Fake is used, of course, so you don't have t o change t he code when it com es t o swapping t he Fake for t he real infrast ruct ure. I hear t he now very fright ened experienced developer exclaim , " Hey, what happened t o Rollback()?" Well, t he way I see it , it 's not im port ant t o have rollback in t he API . I f PersistAll() is responsible for com m it or rollback int ernally, what will happen t hen is t hat when t he consum er get s t he cont rol back from PersistAll(), all changes or none have been persist ed. ( The consum er is not ified about a rollback by an except ion.) The except ion t o t his is when you are aft er BeginReadTransaction() and you t hen want t o cancel. Then you call t he following m et hod: void Clean()
I t will roll back t he ongoing t ransact ion and will also clean t he Unit of Work and t he I dent it y Map. I t 's a good idea t o use Clean() aft er a failed t ransact ion because t here will be no at t em pt at all in NWorkspace t o roll back t he changes in Dom ain Model inst ances. Sure, it depends upon what t he problem wit h t he failed t ransact ion was, but t he sim ple answer is t o rest art . Som e problem s can lead t o a ret ry wit hin PersistAll(). I n t he case of a deadlock, for exam ple, PersistAll() can ret ry a couple of t im es before deciding it was a failure. This is yet anot her t hing t hat sim plifies life for t he consum er program m er so t hat she can focus on what is im port ant t o her, nam ely t o creat e a good user experience, not following lot s and lot s of prot ocols. Now we have t alked a lot about t he funct ionalit y t hat is im port ant for NWorkspace, but not for t he Fake version of NWorkspace. Let 's get back on t rack and focus for a while on t he Fake and it s im plem ent at ion inst ead.
The Implementation of the Fake
I 'm not going t o drag you t hrough t he det ails of t he Fake im plem ent at ionI 'm j ust going t o t alk concept ually about how it 's built , m ost ly in order t o get a feeling for t he basic idea and how it can be used. The fake im plem ent at ion uses t wo layers of I dent it y Maps. The first layer is pret t y sim ilar t o ordinary I dent it y Maps in persist ence fram eworks, and it keeps t rack of all Ent it ies t hat you have read wit hin t he current scenario. The second layer of I dent it y Maps is for sim ulat ing t he persist ent engine, so here t he inst ances aren't kept on a scenario level, but on a global level ( t hat is, t he sam e set of I dent it y Maps for all scenarios) . So when you issue a call t o GetById(), if t he I D is found in t he I dent it y Map for t he request ed t ype, t here won't be a roundt rip t o t he dat abase ( or in case of t he Fake, t here won't be a j um p t o t he second layer of I dent it y Maps) . On t he ot her hand, if t he I D isn't found in t he first layer of I dent it y Maps, it 's fet ched from t he second layer, copied t o t he first layer, and t hen ret urned t o t he consum er. The MakePersistent() is pret t y sim ple; t he inst ance is j ust associat ed wit h t he first layer of I dent it y Maps. And when it 's t im e for PersistAll(), all inst ances in t he first layer are copied t o t he second layer. Sim ple and clean. This describes t he basic funct ionalit y. St ill, it m ight be int erest ing t o say a bit about what 's t roublesom e, also. One exam ple is t hat I don't want t he Fake t o influence t he Dom ain Model in any way at all. I f it does, we're back t o square one, adding infrast ruct ure- relat ed dist ract ions t o t he Dom ain Model, or even worse, Fake- relat ed dist ract ions. One exam ple of a problem is t hat I don't know which is t he I dent it y field( s) of a class. I n t he case of t he real infrast ruct ure, it will probably know t hat by som e m et adat a. I could read t hat sam e m et adat a in t he Fake t o find out , but t hen t he Fake m ust know how t o deal wit h ( t heoret ically) several different m et adat a form at s, and I definit ely don't like t hat . The sim plist ic solut ion I 've adopt ed is t o assum e a propert y ( or field) called Id. I f t he developer of t he Dom ain Model has used anot her convent ion, it could be described t o t he Fake at inst ant iat ion of t he FakeWorkspace . Again, t his was m ore inform at ion t han you probably want ed now, but it leads us t o t he im port ant fact t hat t here are addit ional t hings t hat you can/ need t o do in t he inst ant iat ion phase of t he Fake com pared t o t he infrast ruct ure im plem ent at ions of NWorkspace. To t ake anot her exam ple, you can read from file/ save t o file like t his: //Some early consumer IWorkspace ws = new NWorkspaceFake.FakeWorkspace("c:/temp/x.nworkspace"); //Do stuff... ((NWorkspaceFake.FakeWorkspace)ws). PersistToFile("c:/temp/x.nworkspace");
We t alked quit e a lot about PI and t he Fake m echanism in a way t hat m ight lead you t o believe t hat you m ust go for a PI - support ing infrast ruct ure lat er on if you choose t o use som et hing like t he Fake m echanism now. This is not t he case at all. I t 's not even t rue t hat non- PI - support ing infrast ruct ure m akes it harder for you t o use TDD. I t 's t radit ionally t he case, but not a m ust . Speaking of TDD, has t he Fake affect ed our unit t est s m uch yet ?
Affecting the Unit Tests Nope, cert ainly not all t est s will be affect ed. Most t est s should be writ t en wit h classes in t he Dom ain Model in as isolat ed a way as possible, wit hout a single call t o Reposit ories. For inst ance, t hey should be writ t en during developm ent of all t he logic t hat should t ypically be around in t he Dom ain Model classes. Those t est s aren't affect ed at all. The unit t est s t hat should deal wit h Reposit ories are affect ed, and in a posit ive way. I t m ight be argued t hat t hese are m ore about int egrat ion t est ing, but it doesn't have t o be t hat way. Reposit ories are unit s, t oo, and t herefore t est s on t hem are unit t est s. And even when you do int egrat ion t est ing wit h Reposit ories involved, it 's nice t o be able t o writ e t he t est s early and t o writ e t hem ( and t he Reposit ories) in a way so t hat it is possible t o use t hem when you have infrast ruct ure in place as well. I t hink a nice goal is t o get all t he t est s in good shape so t hat t hey can run bot h wit h t he Fake m echanism and t he infrast ruct ure. That way you can execut e wit h t he Fake m echanism in daily work ( for reasons of execut ion t im e) and execut e wit h t he infrast ruct ure a couple of t im es a day and at t he aut om at ic builds at check in. You can also work quit e a long way wit hout infrast ruct ure in place. You m ust , of course, also t hink a bit about persist ence, and especially for your first DDD proj ect s, it 's im port ant t o work it erat ively from t he beginning. But when you get m ore experience, delaying t he addit ion of t he persist ence will give you t he short est developm ent t im e in t ot al and t he cleanest code. This also gives you t he possibilit y of anot her refact oring rhyt hm , wit h m ore inst ant feedback whet her you like t he result or not . First , you get everyt hing t o work wit h t he Fake ( which is easier and fast er t han get t ing t he whole t hing, including t he dat abase, t o t he right level) , and if you're happy t hen you proceed t o get it all t o work wit h t he infrast ruct ure. I believe t he big win is t hat t his will encourage you t o be keener t o do refact orings t hat would norm ally j ust be a pain, especially when you are unsure about t he out com e. Now you can give t hem a t ry pret t y easily. Of course, t rying t o avoid code duplicat ion is as im port ant for unit t est s as it is for t he " real" code. ( Well, at least close t o as im port ant . There's also a com pet ing st rive t o " show it all" inline.) Therefore I only want t o writ e t he t est s once, but be able t o execut e t hem bot h for t he Fake and t he real infrast ruct ure when in place. ( Please not e t hat t his only goes for som e of t he t est s of course. Most of t he t est s aren't about t he Reposit ories at all so it 's im port ant t hat you part it ion your t est s for t his aspect .)
Structure of the Repository-Related Tests One way of approaching t his is t o writ e a base class for each set of t est s t hat are Reposit oryaffect ing. Then I use t he Tem plat e Met hod pat t ern for set t ing up an IWorkspace t he way I want . I t could look like t his, t aking t he base class first : [TestFixture] public abstract class CustomerRepositoryTestsBase { private IWorkspace _ws; private CustomerRepository _repository; protected abstract IWorkspace _CreateWorkspace(); [SetUp]
public void SetUp() { _ws = _CreateWorkspace(); _repository = new CustomerRepository(_ws); } [TearDown] public void TearDown() { _ws.Clean(); } }
Then t he subclass, which looks like t his: public class CustomerRepositoryTestsFake : CustomerRepositoryTestsBase { protected override IWorkspace _CreateWorkspace() { return new FakeWorkspace(""); } }
OK, t hat was t he plum bing for t he t est s relat ed t o Reposit ories, but what about t he t est s t hem selves? Well, t here are several different st yles from which t o choose, but t he one I prefer is t o define as m uch as possible in t he base class, while at t he sam e t im e m aking it possible t o decide in t he subclass if a cert ain t est should be im plem ent ed or not at t he m om ent . I also want it t o be im possible t o forget t o im plem ent a t est in t he subclass. Wit h t hose requirem ent s in place, m y favorit e st yle looks like t his ( first , how a sim plified t est looks in t he base class) : [Test] public virtual void CanAddCustomer() { Customer c = new Customer(); c.Name = "Volvo"; c.Id = 42; _repository.Add(c); _ws.PersistAll(); _ws.Clean(); //Check Customer c2 = _repository.GetById(c.Id); Assert.AreEqual(c.Name, c2.Name); //Clean up _repository.Delete(c2); _ws.PersistAll(); }
Not e t hat t he second level of I dent it y Maps isn't cleared when new FakeWork-space("") is done because t he second level I dent it y Maps of t he Fake are static and t herefore not affect ed when t he Fake inst ance is recreat ed. That 's j ust how it is wit h a dat abase, of course. Just because you open a
new connect ion doesn't m ean t he Customers t able is cleared. So it 's a good t hing t hat t he Fake works in t his way, because t hen I will need t o clean up aft er t he t est s wit h t he Fake j ust as I will when I 'm using t he t est s wit h a real dat abase, if t hat 's t he approach I 'm choosing for m y dat abase t est ing. Of course, IWorkspace m ust have Delete() funct ionalit y, which I haven't discussed yet , ot herwise it won't be possible t o do t he cleaning up. As a m at t er of fact , in all it s sim plicit y t he Delete() is quit e int erest ing because it requires an I dent it y Map of it s own for t he Fake in t he Unit of Work. I nst ances t hat have been regist ered for delet ion will be held t here unt il PersistAll(), when t he delet ion is done perm anent ly. To support t his, IWorkspace will get a m et hod like t his: void Delete(object o);
Unfort unat ely, it also int roduces a new problem . What should happen t o t he relat ionships for t he delet ed obj ect ? That 's not sim ple. Again, m ore m et adat a is needed t o det erm ine how far t he delet e should cascadem et adat a t hat is around for t he real infrast ruct ure, but not useful here. ( The convent ion current ly used for t he Fake is t o not cascade.) OK, back t o t he t est s. I n t he subclass, I t ypically have one of t hree choices when it com es t o t he CanAddCustomer() t est . The first alt ernat ive is t o do not hing, in which case I 'm going t o run t he t est as it 's defined in t he base class. This is hopefully what I want . The second opt ion should be used if you aren't support ing a specific t est for t he specific subclass for t he t im e being. Then it looks like t his in t he subclass: [Test, Ignore("Not supported yet...")] public override void CanAddCustomer() {}
This way, during t est execut ion it will be clearly signaled t hat it 's j ust a t em porary ignore. Finally, if you " never" plan t o support t he t est in t he subclass, you can writ e it like t his in t he subclass: [Test] public override void CanAddCustomer() { Console.WriteLine ("CanAddCustomer() isn't supported by Fake."); }
OK, you st ill can forget a t est if you do your best . You'd have t o writ e code like t his, skipping t he Test- at t ribut e: public override void CanAddCustomer() {}
There's a solut ion t o t his, but I find t he st yle I have shown you t o be a good balance of am ount of code and t he risk of " forget t ing" t est s. I know, t his wasn't YAGNI , because right now we don't have any im plem ent at ion ot her t han t he Fake im plem ent at ion, but see t his j ust as a quick indicat or for what will happen lat er on.
N ot e This st yle could j ust as well be used in t he case of m ult iple im plem ent at ions for each Reposit ory.
For m any applicat ions, it m ight be close t o im possible t o deal wit h t he whole syst em in t his way, especially lat er on in t he life cycle. For m any applicat ions, it 's perhaps only useful for early developm ent t est ing and early dem os, but even so, if it helps wit h t his, it 's very nice. I f it works all t he way, it 's even nicer. I n real life, t he basics ( t here are always except ions) are t hat I 'm focusing on writ ing t he core of t he t est s against t he Fake im plem ent at ion. I also writ e CRUD t est s against t he Fake im plem ent at ion, but in t hose cases I also inherit t o t est s for using t he dat abase. That way, I t est out t he m apping det ails. That said, no m at t er if you use som et hing like t he ideas of t he abst ract ion layer or not , you will sooner or lat er run int o t he problem s of dat abase t est ing. I asked m y friend Philip Nelson t o writ e a sect ion about it . Here goes.
Database Testing By Philip Nelson At som e point when you are working wit h DDD and all t he various flavors of aut om at ed t est ing, you will probably run int o a desire t o run a t est t hat includes access t o a dat abase. When you first encount er t his, it seem s like no problem at all. Creat e a t est dat abase, point your applicat ion at t he t est dat abase, and st art t est ing. Let 's focus m ore on aut om at ed t est ing, so let 's assum e you wrot e your first t est wit h JUnit or NUnit . This t est j ust loaded a User dom ain obj ect from t he dat abase and verified t hat all t he propert ies were set correct ly. You can run t his t est over and over again and it works every t im e. Now you writ e a t est t o verify t hat you can updat e t he fields, and because you know you have a part icular obj ect in t he dat abase, you updat e t hat . The t est works, but you have int roduced a problem . The read t est no longer works because you have j ust changed t he underlying dat a, and it no longer m at ches t he expect at ions of your first t est . OK, t hat 's no problem ; t hese t est s shouldn't share t he sam e dat a, so you creat e a different User for t he updat e t est . Now bot h t est s are independent of each ot her, and t his is im port ant : at all t im es you need t o ensure t hat t here are no residual effect s bet ween t est s. Dat abase- backed t est s always have precondit ions t hat you m ust m eet . Som ewhere in t he process of writ ing your t est s, you m ust account for t he fact t hat you have t o reset t he dat abase t o m eet t he precondit ions t he t est expect s. More det ails on t hat t o com e. At som e point , you will have writ t en t est s for all t he basic User obj ect life cycle st at es, let 's say creat e, read, updat e, and delet e ( CRUD) . I n your applicat ion, t here m ay very well be ot her dat abasebacked operat ions. For exam ple, t he User obj ect m ay help enforce a policy of m axim um failed login at t em pt s. As you t est t he updat ing of t he failed login at t em pt s and last login t im e fields, you realize t hat your updat e act ually isn't working. The updat e t est didn't cat ch t he problem because t he dat abase already had t he field set t o t he expect ed value. As t he sm art person you are, you figured t his out very quickly. However, young Joe down t he hall has j ust spent four hours working on t he problem . His coding skills aren't so m uch t he problem as m uch as his underst anding of how t he code connect s t o t he dat abase and how t o isolat e t he dat a in t his t est from all t he ot her t est dat a he now has. He j ust doesn't not ice t hat t he LastUpdateDate field is not reset bet ween t est s. Aft er all, t he code is designed t o hide t he dat abase as an im plem ent at ion det ail, right ? At t his point , you st art t o realize t hat j ust having separat e t est dat a for each t est is going t o be m ore com plicat ed t han you want it t o be. You m ay or m ay not have underst ood as clearly as necessary j ust how im port ant it was t o reset t he dat a bet ween t est s, but now you do. Fort unat ely, your xUnit t est fram ework has j ust t he t hing for you. There is a set up and t eardown code block t hat is j ust m ade for t his sort of t hing. But like m ost t hings in program m ing, t here is m ore t han one way t o do it , and each has it s st rengt hs and weaknesses. You m ust underst and t he t radeoffs and det erm ine t he balance t hat suit s you best . I would cat egorize t he t echniques available t o you in four ways: Reset t he dat abase before each t est . Maint ain t he st at e of t he dat abase during t he run.
Reset t he dat a for j ust t he t est or set of t est s you are running before t he run. Separat e t he t est ing of t he unit from t he t est ing of t he call t o t he dat abase.
Reset the Database Before Each Test At first glance, t his m ight seem t he m ost desirable, but possibly t he m ost t im e- consum ing, opt ion. The plus t o t his approach is t hat t he whole syst em is in a known st at e at t he st art of t he t est . You don't have t o worry about st range int eract ions during t est runs because t hey all st art at t he sam e place. The downside is t im e. Aft er you have got t en past t he init ial part s of your proj ect , you will find yourself wait ing while your t est suit e runs. I f you are doing unit t est ing and running your t est s aft er each change, t his can very quickly becom e a significant part of your t im e. There are som e opt ions t hat can help. How t hey apply t o you will depend on m any t hings, from t he t ype of archit ect ure you use t o t he t ype of dat abase syst em you use. One sim ple but slow approach is t o rest ore t he dat abase from backup before each t est . Wit h m any dat abase syst em s, t his is not pract ical because of t he am ount of t im e it t akes. However, t here are som e syst em s where it is possible. I f you are program m ing against a file- based dat abase, for exam ple, you m ay only need t o close your connect ions and copy a file t o get back t o t he original st at e. Anot her possibilit y is an in- m em ory dat abase, such as HSQL for Java, t hat can be rest ored very quickly. Even if you are using a m ore st andard syst em like Oracle or SQL Server, if your design for dat a access is flexible enough, you m ay be able t o swit ch t o an in- m em ory or file- based dat abase for your t est s. This is especially t rue if you are using an O/ R Mapper t hat t akes t he responsibilit y of building t he act ual SQL calls for you and knows m ult iple dialect s. The proj ect DbUnit offers anot her way t o reset a dat abase before t he t est runs. Essent ially, it 's a fram ework for JUnit t hat allows you t o define " dat a set s" t hat are loaded on clean t ables before each t est run. Ruby on Rails has a sim ilar syst em t hat allows you t o describe your t est class dat a in t he open YAML ( YAML Ain't Markup Language) form at and apply it during t est set up. These t ools can work well, but you m ay st art running int o problem s as t hese dat abase insert s m ay be logged operat ions t hat can be t oo slow. Anot her approach t hat could work for you is t o use bulk load ut ilit ies t hat m ay not be as ext ensively logged. Here is how t his m ight work for SQL Server. First , use a dat abase recovery opt ion on your t est dat abase of Sim ple Recovery. This elim inat es m ost logging and im proves perform ance. Then, during t est design, do t he following: I nsert dat a int o a t est dat abase t hat will only act as a source of clean dat a for your t est s Call t ransact sql com m ands t hat export t he t est dat a t o files Writ e t ransact sql com m ands t o do bulk insert of t he dat a in t hese files Then during t he t est set up m et hod, do t he following: Truncat e all your t ables. This is not a logged operat ion and can be very fast . I ssue t he bulk copy com m ands creat ed earlier t o load t he t est dat a int o t he t est dat abase. A variat ion of t his t echnique is t o set up your t est dat abase and load t he init ial t est dat a using norm al dat a m anagem ent t echniques. Then, provided your dat abase support s such an operat ion, det ach t he underlying dat a files from t he server. Make a copy of t hese files as t hese will be t he source of your clean t est dat a. Then, writ e code t hat allows you t o Det ach t he server from it s dat a files
Copy t he clean t est dat a over t he act ual server files At t ach t he dat abase t o t he copy I n m any cases, t his operat ion is very fast and can be run in t he t est fixt ure set up or, if t he am ount of dat a isn't t oo large, in t he t est set up it self. Yet anot her variat ion support ed by som e of t he O/ R Mapping t ools is t o build t he dat abase schem a from m apping inform at ion. Then you can use your Dom ain Model in t est set up t o populat e dat a as needed for each t est suit e or possibly for each t est fixt ure.
Maintain the State of the Database During the Run You are probably t hinking " But t hat is a dat a m anagem ent exercise! " , and you are correct . There is anot her t echnique t hat is even sim pler t o execut e. Essent ially, what you do is t o run each t est in a t ransact ion and t hen roll back t he t ransact ion at t he end of t he t est . This could be a lit t le challenging because t ransact ions are not oft en exposed on public int erfaces, but again, t his all depends on your archit ect ure. For MS .NET environm ent s, Roy Osherove cam e up wit h a very sim ple solut ion t hat draws on ADO.NET's support for COM+ t ransact ion enlist m ent . What t his t ool does is allow you t o put a [Rollback] at t ribut e on specific t est m et hods, allowing t hose m et hods t o be run in t heir own COM+ t ransact ion. When t he t est m et hod finishes, t he t ransact ion is t hen rolled back aut om at ically for you wit h no code on your part and independent of t he t ear- down funct ionalit y of t he class. What 's really great about t his t echnique is t hat your t est s can be blissfully unaware of what 's happening underneat h t hem . This funct ionalit y has been packaged up in a proj ect called Xt Unit [ Xt Unit ] and is an ext ension t o t he NUnit t est ing fram ework. This is by far t he sim plest approach t o keeping your dat abase in prist ine shape. I t does com e wit h a price, t hough. Transact ions are by t heir nat ure logged, which increases t he execut ion t im e. COM+ t ransact ions use t he Dist ribut ed Transact ion Coordinat or, and dist ribut ed t ransact ions are slower t han local t ransact ions. The com binat ion can be brut al t o t he execut ion speed of your t est suit e. Nat urally, if you are t est ing t ransact ion sem ant ics t hem selves, you m ay have som e addit ional problem s wit h t his t echnique. So depending on t he specifics of your proj ect , t his can be a really great solut ion or a solut ion in need of a replacem ent as t he num ber of t est s grows. Fort unat ely, you would not necessarily have t o rewrit e m uch code should t est speed becom e a problem because t he solut ion is t ransparent t o t he t est . You will eit her be able t o live wit h it , or you will have t o adopt ot her t echniques as your proj ect grows.
Reset the Data Used by a Test Before the Test This approach relieves you and your syst em of having t o reset t he ent ire dat abase before t he t est call. I nst ead, you issue a set of com m ands t o t he dat abase before or aft er t he t est , again t ypically in t he set up and/ or t eardown m et hods. The good news is t hat you have m uch less t o do for a single class of t est s. The previous t echniques can st ill be used, but now you have t he addit ional opt ion of issuing sim ple insert , updat e, or delet e st at em ent s as well. This is now less painful, even if logged, because t he im pact is less. Again, reducing t he logging effort wit h dat abase set up opt ions is st ill a good idea if your syst em support s it . There is a downside, t hough. As soon as you m ake t he leap t o assum ing you know exact ly what dat a will be affect ed by your t est , you have t ied yourself int o underst anding what not j ust t he code under t est is doing wit h dat a, but also what addit ional code t he code under t est calls out t o. I t m ay get changed wit hout you not icing it . Test s m ay break t hat are not an indicat ion of broken code. A bug will get filed t hat m ay not be a bug at all but is m aint enance work j ust t he sam e.
I t is possible t o use t his t echnique successfully, but aft er m any years of t est writ ing, I have found t his approach t o be t he m ost likely t o break wit hout good reason. You can m inim ize t his, t hough. I f you m ock out t he classes your code under t est calls out t o, t hat code won't affect t he dat abase. This is in spirit m uch closer t o what is m eant by a unit t est . Mocking is not useful if you are aut om at ing syst em t est s where t he int eract ions bet ween real classes are exact ly what you want t o t est . At any rat e, doing t his requires an archit ect ure decision t o allow for easy subst it ut ion of ext ernal classes by t he t est ing fram ework. I f you are going t o do t hat , you have nat urally found yourself working t oward t he final opt ion I am covering here.
Don't Forget Your Evolving Schema! You will no doubt find yourself m aking changes t o t he dat abase over t im e. While in t he init ial st ages you m ay want t o j ust m odify t he dat abase direct ly and adj ust your code as needed, aft er you have a released syst em , it 's t im e t o t hink about how t hese changes should becom e part of your process. Working wit h a t est syst em act ually m akes t his easier. I prefer t he approach of creat ing and running alt er script s against your dat abase. You could run t hese script s im m ediat ely aft er your dat abase reset , but because all t hat dat abase st at e is cont ained in your source cont rolled developm ent environm ent , it probably m akes sense t o develop t he script s and use t hem t o m odify your t est environm ent . When your t est s pass, you check it all in and t hen have t he script s aut om at ically set up t o run against your QA environm ent as needed. I t 's even bet t er if t his happens on your build server because t he running of t he alt er script s is t hen t est ed oft en before it 's applied t o your live syst em s. Of course, t hat assum es you regularly reset your QA environm ent t o m at ch your live environm ent . I 'm sure t here are m any variat ions of t his process, but t he m ost im port ant t hing is t o plan ahead t o ensure a reliable release of bot h code and dat abase changes.
Separate the Testing of the Unit from the Testing of the Call to the Database Few t hings in t he Agile com m unit y's m ailing list s and forum s generat e m ore discussion t han t he t est ing of code t hat int eract s wit h dat abases. Am ong t hose who are pract icing ( and st ill defining) t he t echniques of TDD, it is a highly held value t hat all code can be t est ed independent ly as unit s as t hey are writ t en. I n t he spirit of TDD, it wouldn't m ake sense t o writ e a t est for a class and st art out your im plem ent at ion wit h a direct call t o a dat abase. I nst ead, t he call is delegat ed out t o a different obj ect t hat abst ract s t he dat abase, which is replaced wit h a st ub or a m ock as you writ e t he t est . Then you writ e t he code t hat act ually im plem ent s t he dat abase abst ract ion, and t est t hat it calls t he underlying dat a access obj ect s correct ly. Finally, you would writ e a few t est s t hat t est t hat your infrast ruct ure t hat connect s real dat abase calls t o your code works correct ly. As t he old saying goes, m ost problem s in coding can be solved wit h anot her layer of indirect ion. As always, t he devil's in t he det ails. First , let 's clarify a few definit ions. A st ub is a replacem ent piece of code t hat does j ust enough t o not cause problem s for t he caller and no m ore. A st ub for a dat abase call, such as a call t o a JDBC Statement, would be accom plished by having a bare- bones class t hat im plem ent s t he Statement's int erface and sim ply ret urns a ResultSet when called, possibly ignoring t he param et ers passed and cert ainly not execut ing a dat abase call. A m ock Statement would do all t hat and also allow t he set t ing of expect at ions. I 'll say m ore on t hat in a m om ent . Like a st ub, t he t est would use t he m ock com m and inst ead of t he real com m and, but when t he t est was com plet e, it would " ask" t he m ock com m and t o " verify" t hat it was called
correct ly. The expect at ions for a m ock Statement would be values for any param et ers t he call needed, t he num ber of t im es t hat executeQuery was called, t he correct SQL t o be passed t o t he st at em ent and so on. I n ot her words, you t ell t he m ock what t o expect . Then you ask it t o verify t hat it did receive t hese expect at ions aft er t he t est is com plet e. When you t hink about it , I t hink you have t o agree t hat a unit t est for a User class should not have t o concern it self wit h what t he dat abase does. So long as t he class int eract s wit h t he dat abase abst ract ion correct ly, we can assum e t he dat abase will do it s part correct ly or at least t hat t est s t o your dat a access code will verify t hat fact for you. You j ust have t o verify t hat t he code correct ly passes all t he fields t o t he dat abase access code, and you have done all you need t o do. I f only it could always be t hat sim ple! To t ake t his t o it s logical conclusion, you m ight end up writ ing m ock im plem ent at ions of m any of t he classes in your code. You will have t o populat e t he m em bers and propert ies of t hose m ocks wit h at least enough dat a t o sat isfy t he requirem ent s of your t est s. That 's a lot of code, and an argum ent could be m ade t hat j ust having t o support t hat m uch code will m ake your code harder t o m aint ain. Consider t his alt ernat ive. There are som e new fram eworks available in a variet y of languages t hat allow you t o creat e m ock obj ect s from t he definit ions of real classes or int erfaces. These dynam ically creat ed m ocks allow you t o set expect at ions, set expect ed ret urn values and do verificat ion wit hout writ ing m uch code t o m ock t he class it self. Known collect ively as Dynam ic Mocks, t he t echnique allows you t o sim ply pass in t he class t o m ock t o a fram ework and get back an obj ect t hat will im plem ent it s int erface. There are m any ot her sources of inform at ion on how t o m ock code, but m uch less on how t o effect ively m ock dat abase access code. Dat a access code t ends t o have all t hese m oving part s t o deal wit h: connect ions, com m and obj ect s, t ransact ions, result s, and param et ers. The dat a access libraries t hem selves have driven t he creat ion of a wide variet y of dat a access helpers whose aim it is t o sim plify t his code. I t seem s t hat none of t hese t ools, t he helpers, or t he underlying dat a access libraries like JDBC or ADO.NET were writ t en wit h t est ing in m ind. While m any of t hese t ools offer abst ract ions on t he dat a access, it t urns out t o be fairly t ricky work t o m ock all t hose m oving part s. There is also t hat issue of having t o t est t he m apping of all t hose fields bet ween t he dat a access code and rest of your classes. So here are som e pieces of advice t o help you t hrough it . Test everyt hing you can wit hout act ually hit t ing your dat abase access code. The dat a access code should do as lit t le as you can get away wit h. I n m ost cases, t his should be CRUD. I f you are able t o t est all t he funct ionalit y of your classes wit hout hit t ing t he dat abase, you can writ e dat abase t est s t hat only exercise t hese sim pler CRUD calls. Wit h t he aid of helper m et hods you m ay be able t o verify t hat t he before and aft er set of fields m at ch your expect at ions by using reflect ion t o com pare t he obj ect s rat her t han hand coding all t he propert y t est s. This can be especially helpful if you use an O/ R Mapper, such as Hibernat e, where t he dat a access is very nicely hidden, but t he m apping file it self needs t o be t est ed. I f all t he ot her funct ionalit y of t he class is verified wit hout t he dat abase hit , you oft en only need t o verify t hat t he class's CRUD m et hods and t he m apping are working correct ly. Test t hat you have called t he dat a access code correct ly separat ely from t est ing t he dat abase code it self. For exam ple, if you have a User class t hat saves via a UserRepository, or perhaps a Dat a Access Layer in nTier t erm inology, all you need t o t est is t hat t he UserRepository is called correct ly by your upper- level classes. Test s for t he UserRepository would t est t he CRUD funct ionalit y wit h t he dat abase. To t est cert ain t ypes of dat a access, t hese sim ple CRUD t est s m ay not be adequat e. For exam ple, calls t o st ored procedures t hat aren't direct ly relat ed t o class persist ence fall in t his cat egory. At som e point , you m ay need t o t est t hat t hese procedures are called correct ly, or in your t est you m ay need dat a back from one of t hese calls for t he rest of your t est . Here are som e general t echniques t o consider in t hose cases where you really are going t o m ock JDBC, ADO.NET, or som e ot her dat a access library direct ly. You m ust use fact ories t o creat e t he dat a access obj ect s and program against int erfaces rat her t han
concret e t ypes. Fram eworks m ay provide fact ories for you, as t he Microsoft Dat a Access Applicat ion Block does in it s m ore recent versions. However, you also need t o be able t o use t hese fact ories t o creat e m ock im plem ent at ions of t he dat a access classes, som et hing not support ed out of t he box by m any fact ory fram eworks. I f you have a fact ory t hat can be configured by your t est code t o provide m ocks, you can subst it ut e a m ock version of t hese dat a access classes. Then you can verify virt ually any t ype of dat abase call. You st ill m ay need m ock im plem ent at ions of t hese classes, t hough. For ADO.NET, t hese can be obt ained from a proj ect called .NET Mock Obj ect s [ MockObj ect s] . Versions for ot her environm ent s m ay also be available. The only com binat ion of a fact ory- based fram ework t hat can work wit h m ocks direct ly t hat I am aware of is t he SnapDAL fram ework [ SnapDAL] t hat builds on .NET Mock Obj ect s t o supply m ock im plem ent at ions of t he ADO.NET generic classes and was built t o fully support m ock obj ect s for t he ADO.NET generic int erfaces. Whet her you can use t hese fram eworks or not depends on m any fact ors, but one way or anot her, you will need your applicat ion t o support a fact ory t hat can ret urn a real or a m ock of your dat a access classes. When you get t o a posit ion where you can creat e m ock inst ances of your dat a access classes, you can now use som e or all of t he following t echniques t o t est : The m ock ret urns result set s, act ing as a st ub for your code. The result set get s it s dat a from t he t est code in st andard m ock obj ect st yle. The m ock can ret urn t est dat a from files, such as XML files or spreadsheet s of accept ance t est dat a provided by your business unit s. The m ock can ret urn t est dat a from an alt ernat e dat abase or a m ore refined query t han t he real query under t est . I f you can subst it ut e a dat a access com m and, you can " record" t he dat a access and lat er " play it back" from a m ock obj ect in your t est s because you would have access t o all t he param et ers and t he ret urned result s. Mocks can have expect at ions verified of im port ant t hings like t he connect ion being opened and closed, t ransact ions com m it t ed or rolled back, except ions generat ed and caught , and dat a readers read, and so on. I hope t hese ideas can get you st art ed wit h an appreciat ion for t he m any possibilit ies t here are for t est ing dat abase- connect ed code. The t ext was writ t en basically in order of com plexit y, wit h dat abase reset t ing or t he rollback t echniques being t he easiest t o im plem ent . Fully separat ing out t he dat a access code from t he rest of your code will always be a good m ove and will im prove your t est ing success and t est speed. At t he m ost fine- grained level, allowing for m ock versions of your act ual dat abase calls offers great flexibilit y in how you gat her, provide, and organize t est dat a. I f you are working wit h legacy code t hat uses t he lower- level dat a access libraries of your language, you would probably m ove t oward t his sort of approach. I f you are st art ing a new applicat ion, you can probably st art wit h t he idea of sim ply reset t ing your dat abase bet ween t est s and enhance your t est st ruct ure as t est speed begins t o affect your product ivit y. Thanks, Phil! Now we are arm ed t o deal wit h t he problem of dat abase t est ing no m at t er what approach we choose.
N ot e Neeraj Gupt a com m ent ed on t his by saying, " You can use t he Flashback feat ure of Oracle dat abase t o bring t he dat abase back t o t he previous st at e for t he t est ing."
We have com e quit e a long way, but t here is one very big piece of t he preparing for infrast ruct ure puzzle t hat we haven't dealt wit h at all. How did I solve _GetNumberOfStoredCustomers? What I 'm m issing is obviously querying!
Querying Querying is ext rem ely different in different infrast ruct ure solut ions. I t 's also som et hing t hat great ly risks affect ing t he consum er. I t m ight not be apparent at first when you st art out sim ply, but aft er a while you'll oft en find quit e a lot of querying requirem ent s, and while you are fulfilling t hose, you're norm ally t ying yourself t o t he chosen infrast ruct ure. Let 's t ake a st ep back and t ake anot her solut ion t hat is not query- based. Earlier, I showed you how t o fet ch by ident it y in a Reposit ory wit h t he following code: //OrderRepository public Order GetOrder(int orderNumber) { return (Order)_ws.GetById(typeof(Order), orderNumber); }
However, if OrderNumber isn't an ident it y, t he int erface of t he Reposit ory m et hod m ust clearly change t o ret urn a list inst ead, because several orders can have ordernum ber 0 before t hey have reached a cert ain st at e. But t hen what ? GetById() is useless now, because OrderNumber isn't an I dent it y ( and let 's assum e it 's not unique because I said t he answer could be a list ) . I need a way t o get t o t he second layer of I dent it y Maps of t he Fake for t he Orders. Let 's assum e I could do t hat wit h a GetAll(Type typeOfResult) like t his: //OrderRepository public IList GetOrders(int orderNumber) { IList result = new ArrayList(); IList allOrders = _ws.GetAll(typeof(Order)); foreach (Order o in allOrders) { if (o.OrderNumber == orderNumber) result.Add(o); } return result; }
I t 's st ill pret t y silly code, and it 's definit ely not what you want t o writ e when you have infrast ruct ure in place, at least not for real- world applicat ions.
Single-Set of Query Objects Just as wit h t he Reposit ories, it would be nice t o writ e t he queries " correct ly" from day one in a single im plem ent at ion which could be used bot h wit h t he Fake and wit h t he real infrast ruct ure. How can we deal wit h t hat ?
Let 's assum e t hat we want t o work wit h Query Obj ect s [ Fowler PoEAA] ( encapsulat e queries as obj ect s, providing obj ect - orient ed synt ax for working wit h t he queries) and we also change t he signat ure from GetAll() t o call it GetByQuery() and t o let it t ake an IQuery ( as defined in NWorkspace) inst ead. The code could now look like t his: //OrderRepository public IList GetOrders(int orderNumber) { IQuery q = new Query(typeof(Order)); q.AddCriterion("OrderNumber", orderNumber); return _ws.GetByQuery(q); }
OK, t hat was pret t y st raight forward. You j ust creat e an IQuery inst ance by saying which t ype you expect in t he result . Then you set t he crit eria you want for holding down t he size of t he result set as m uch as possible, t ypically by processing t he query in t he dat abase ( or in t he Fake code, in t he case of when you're using t he Fake im plem ent at ion) .
N ot e We could pret t y easily m ake t he query int erface m ore fluent , but let 's st ay wit h t he m ost basic we can com e up wit h for now.
That was what t o do when you want t o inst ant iat e part of t he Dom ain Model. Let 's get back t o t he _GetNumberOfStoredCustomers() t hat we t alked about earlier. How could t hat code look wit h our newly added querying t ool? Let 's assum e it calls t he Reposit ory and a m et hod like t he following: //CustomerRepository public int GetNumberOfStoredCustomers() { return _ws.GetByQuery(new Query(typeof(Customer))).Count; }
I t works, but for product ion scenarios t hat solut ion would reduce every DBA t o t ears. At least it will if t he result is a SELECT t hat fet ches all rows j ust so you can count how m any rows t here are. I t 's j ust not accept able for m ost sit uat ions. We need t o add som e m ore capabilit ies in t he querying API . Here's an exam ple where we have t he possibilit y of ret urning sim ple t ypes, such as an int , com bined wit h an aggregat e query ( and t his t im e aggregat e isn't referring t o t he DDD pat t ern Aggregat e, but , for exam ple, t o a SUM or AVG query in SQL) : //CustomerRepository public int GetNumberOfStoredCustomers() { IQuery q = new Query(typeof(Customer), new ResultField("CustomerNumber", Aggregate.Count)); return (int)_ws.GetByQuery(q)[0]; }
A bit raw and im m at ure, but I t hink t hat t his should give you an idea of how t he basic querying API in NWorkspace is designed. I t would be nice t o have a st andard querying language, wouldn't it ? Perhaps t he absence of one was what m ade Obj ect Dat abases not really t ake off. Sure, t here was Obj ect Query Language ( OQL) , but I t hink it cam e in pret t y lat e, and it was also a pret t y com plex st andard t o im plem ent . I t 's com pet ent , but com plex. ( Well, it was probably a com binat ion of t hings t hat hindered Obj ect Dat abases from becom ing m ainst ream ; isn't it always?) I 'll t alk m ore about Obj ect Dat abases in Chapt er 8, " I nfrast ruct ure for Persist ence." What I now want , t hough, is a querying st andard for persist ence fram eworks som et hing as widespread as SQL, but for Dom ain Models. Unt il we have such a st andard, t he NWorkspace version could bridge t he gap, for m e at least . I s t here a cost for t hat ? I s t here such a t hing as a free lunch?
The Cost for Single-Set of Query Objects Of course t here's a cost for a t ransform at ion, and t hat goes for t his case, t oo. First , t here's a cost in perform ance for going from an IWorkspace im plem ent at ion t o t he infrast ruct ure solut ion. However, t he perform ance cost will probably be pret t y low in t he cont ext of end- t o- end scenarios. Then t here's t he cost of loss of power because t he NWorkspace- API is sim plified, and com pet ent infrast ruct ure solut ions have m ore t o offer and t hat is probably m uch worse. Yet anot her cost is t hat t he API of NWorkspace it self is pret t y raw and perhaps not as nice as t he querying API of your infrast ruct ure solut ion. OK, all t hose cost s sound fair, and if t here's a lot t o be gained, I can live wit h t hem . I left out one very im port ant det ail before about querying and t he I dent it y Map: bypassing t he cache when querying or not .
Querying and the Cache I m ent ioned earlier t hat when you do a GetById(), if t hat operat ion m ust be fulfilled by going t o persist ence, t he fet ched inst ance will be added t o t he I dent it y Map before being ret urned t o t he consum er. That goes for GetByQuery() as well; t hat is, t he inst ances will be added t o t he I dent it y Map. However, t here's a big difference in t hat t he GetByQuery() won't invest igat e t he I dent it y Map before hit t ing persist ence. The reason is part ly t hat we want t o use t he power of t he backend, but above all t hat we don't know if we have all t he necessary inform at ion in t he I dent it y Map ( or cache if you will) for fulfilling t he query. To find out if we have t hat , we need t o hit t he dat abase. This brings us t o anot her problem . GetById() st art s wit h t he I dent it y Map; GetByQuery() does not . This is t ot ally different behavior, and it act ually m eans t hat GetByQuery() bypasses t he cache, which is problem at ic. I f you ask for t he new Customer Volvo t hat has been added t o t he I dent it y Map/ Unit of Work, but has not been persist ed, it will be found if you ask by I D but not when you ask by nam e wit h a query. Weird. To t ell you t he t rut h, it was a painful decision, but I decided t o let GetByQuery() do an im plicit PersistAll() by default before going t o t he dat abase. ( There's an override t o GetByQuery() t o avoid t his, but again, it 's t he default t o im plicit ly call PersistAll().) I cam e t o t he conclusion t hat t his default st yle is probably m ost in line wit h t he rest of NWorkspace and it s goal of sim plicit y and t he lessened risk of errors. This is why I m ade som e sacrifices wit h t ransact ional sem ant ics. Som e m ight argue t hat t his violat es t he principle of least am ount of surprise. But I t hink it depends on your background, what is surprising in t his case.
The biggest drawback is definit ely t hat when doing GetByQuery(), you m ight get save- relat ed except ions. What a painful decisionbut I need t o decide som et hing for now t o m ove on. Do you rem em ber t he sim ple and unopt im ized version of t he _GetNumberOf-StoredCustomers()? I t 's not j ust slowit m ight not work as expect ed when it looks like t his ( which goes for t he opt im ized version as well: public int GetNumberOfStoredCustomers() { return _ws.GetByQuery(new Query(typeof(Customer))).Count; }
The reason it won't work for m y purpose is t hat GetByQuery() will do t hat im plicit PersistAll(). I nst ead, an overload m ust be used like t his, where false is for t he implicitPersistAll param et er: public int GetNumberOfStoredCustomers() { return _ws.GetByQuery(new Query(typeof(Customer) , false)).Count; }
N ot e And of course we could ( and should) use t he aggregat e version inst ead. The focus here was how t o deal wit h im plicit PersistAll().
All t his affect s t he program m ing m odel t o a cert ain degree. First of all, you should definit ely t ry t o adopt a st yle of working in blocks when it com es t o querying and changing for t he sam e workspace inst ance. So when you have m ade changes, you should be happy about t hem before querying because querying will m ake t he changes persist ent .
N ot e You m ight be right . I m ight be responsible for fost ering a sloppy st yle of consum er program m ers. They j ust code and it works, even t hough t hey forget about saving explicit ly and so on.
An unexpect ed and unwant ed side effect is t hat you can get t ot ally different except ions from GetByQuery() from what you expect because t he except ion m ight really com e from PersistAll(). Therefore, it 's definit ely a good idea t o do t he PersistAll() explicit ly in t he consum er code anyway. And again, if you hat e t his behavior, t here's not hing t o st op you from using t he overload. ( Act ually, wit h GetById() you could do it t he ot her way around, so t hat an overload goes t o t he dat abase regardless, wit hout checking t he I dent it y Map.) "I don't care about m y own t ransient work; I want t o know what 's in t he dat abase. "
That was a bit about how querying works in relat ion t o t he I dent it y Map. Next up is where t o host t he quer ies.
Where to Locate Queries As I see it , we have at least t he following t hree places in which t o host query inst ances: I n Reposit ories I n consum ers of Reposit ories I n t he Dom ain Model Let 's discuss t hem one by one.
In Repositories Probably t he m ost com m on place t o set up queries is in t he Reposit ories. Then t he queries becom e t he t ool for fulfilling m et hod request s, such as GetCustomersByName() and GetUndeliveredOrders(). That is, t he consum er m ight send param et ers t o t he m et hods, but t hose are j ust ordinary t ypes and not Query Obj ect s. The Query Obj ect s are t hen set up in accordance wit h t he m et hod and possible param et er values.
In Consumers of Repositories I n t he second case, t he queries are set up in t he consum ers of t he Reposit ories and sent t o t he Reposit ories as param et ers. This is t ypically used in cases of highly flexible queries, such as when t he user can choose t o fill in any fields in a large filt ering form . One such t ypical m et hod on a Reposit ory could be nam ed GetCustomersByFilter(), and it t akes an IQuery as param et er.
In the Domain Model Finally, it m ight be int erest ing t o set up t yped Query Obj ect s in t he Dom ain Model ( st ill queries t hat im plem ent IQuery of NWorkspace) . The consum er st ill get s t he power of queries t o be used for sending t o Reposit ories, for exam ple, but wit h a highly int uit ive and t ypesafe API . How t he API looks is, of course, t ot ally up t o t he Dom ain Model developer. I nst ead of t he following sim ple t ypeless query: //Consumer code IQuery q = new Query(typeof(Customer)); q.AddCriterion("Name", "Volvo");
t he consum er could set up t he sam e query wit h t he following code: //Consumer code CustomerQuery q = new CustomerQuery();
q.Name.Eq("Volvo");
I n addit ion t o get t ing sim pler consum er code, t his also furt her encapsulat es t he Dom ain Model. I t 's also about lessening t he flexibilit y, and t hat is very good. Don't m ake everyt hing possible. Assum ing you like t he idea of Dom ain Model- host ed queries, which queries do we need and how m any ?
Aggregates as a Tool Again That last quest ion m ay have sounded quit e open, but I act ually have an opinion on t hat . Guess what ? Yes, I t hink we should use Aggregat es. Each of your Aggregat es is a t ypical candidat e for having Query Obj ect in t he Dom ain Model. Of course, you could also go t he XP rout e of creat ing t hem when needed for t he first t im e, which is probably bet t er. Speaking of Aggregat es, I 'd like t o point out again t hat I see Aggregat es as t he default m echanism for det erm ining how big t he loadgraphs should be.
N ot e Wit h loadgraph, I m ean how far from t he t arget inst ance we should load inst ances. For exam ple, when we ask for a cert ain order, should we also load it s customer or not ? What about it s orderLines?
And when t he default isn't good enough perform ance- wise, we have lot s of room for perform ance opt im izat ions. A t ypical exam ple is t o not load com plet e graphs when you need t o list inst ances, such as Orders. I nst ead, you creat e a cut down t ype, perhaps called OrderSnapshot , wit h j ust t he fields you need in your t ypical list sit uat ions. I t 's also t he case t hat t hose list s won't be int egrat ed wit h t he Unit of Work and I dent it y Map, which probably is exact ly what you want , again because of perform ance reasons ( or it m ight creat e problem sas always, it depends) . An abst ract ion layer could support creat ing such list s so t hat your code will be agnost ic about t he im plem ent at ion of t he abst ract ion layer at work. I t could look like t his: //For example OrderRepository.GetSnapshots() IQuery q = new Query(typeof(Order), typeof(OrderSnapshot) , new string[]{"Id", "OrderNumber", "Customer.Id" , "Customer.Name"}); q.AddCriterion("Year", year); result _ws.GetByQuery(q);
For t his t o work, OrderSnapshot m ust have suit able const ruct or, like t his ( assum ing here t hat t he I ds are im plem ent ed as Guids) :
//OrderSnapshot public OrderSnapshot(Guid id, int orderNumber , Guid customerId, string customerName)
N ot e Get t ing t ype explosion is com m only referred t o as t he Achilles heel of O/ R Mappers, which I provided an exam ple of earlier. I n m y experience, t he problem is t here, but not anyt hing we can't deal wit h. First , you do t his only when you really have t o, so not all Aggregat e root s in your m odel will have a snapshot class. Second, you can oft en get away wit h a single snapshot class for several different list scenarios. The opt im izat ion effect of doing it at all is oft en significant enough t hat you don't need t o go furt her.
Anot her com m on approach is t o use Lazy Load for t uning by loading som e of t he dat a j ust in t im e. ( We'll t alk m ore about t his in a lat er chapt er.) And if t hat isn't powerful enough, you can writ e m anual SQL code and inst ant iat e t he snapshot t ype on your own. Just be very clear t hat you are st art ing t o act ively use t he dat abase m odel as well at t hat point in t im e.
One or Two Repository Assemblies? Wit h all t his in place, you underst and t hat you have m any different possibilit ies for how t o st ruct ure t he Reposit ories, but you m ight wonder how it 's done in real- world proj ect s. I n t he m ost recent large proj ect of m ine ( which is in product ionit 's not a t oy proj ect ) , I use a single set of Reposit ories, bot h for Fake execut ion and execut ion against t he dat abase. There are a few opt im izat ions t hat use nat ive SQL, but I used a lit t le hack t here so t hat if t he opt im ized m et hod finds an inj ect ed connect ion st ring, it calls out t o anot her m et hod where I 'm t ot ally on m y own. Ot herwise, t he un- opt im ized code will be used inst ead. That way, t he Fake will use t he un- opt im ized version, and t he dat abase- relat ed code will t ypically use t he opt im ized version. Again, t his is only used in a handful of cases. Not ext rem ely nice and clean, but it works fine for now.
Specifications as Queries Yet anot her approach for querying is t o use t he Specificat ion pat t ern [ Evans DDD] ( encapsulat e concept ual specificat ions for describing som et hing, such as what a cust om er t hat isn't allowed t o buy m ore " looks like" ) . The concept get s a describing nam e and can be used again and again. Using t his approach is sim ilar t o creat ing t ype safe Dom ain Model- specific queries, but it goes a st ep furt her because it 's not generic querying t hat we are aft er, but very specific querying, based on
dom ain- specific concept s. This is one level higher t han t he query definit ions t hat live in t he Dom ain Model, such as CustomerQuery . I nst ead of exposing generic propert ies t o filt er by, a specificat ion is a concept wit h a very specific dom ain m eaning. A com m on exam ple is t he concept of a gold cust om er. The specificat ion describes what is needed for a cust om er t o be a gold cust om er. The code get s very clear and purposeful because t he concept s are caught and described as Specificat ion classes. Even t hose Specificat ion classes could very well spit out IQuery so t hat you can use t hem wit h GetByQuery() of IWorkspace ( or equivalent ) if you like t he idea of an abst ract ion layer.
Other Querying Alternatives So far we have j ust t alked about Query Obj ect s as being t he query language we need ( som et im es em bedded in ot her const ruct s, t hough) . As a m at t er of fact , when t he queries get com plex, it 's oft en m ore powerful t o be able t o writ e t he queries in, for exam ple, a st ring- based language sim ilar t o SQL. I haven't t hought about any m ore query languages for NWorkspace. But perhaps I can som eday t alk som e people int o adding support for NWorkspace queries as out put from t heir queries. That way, t heir queries ( such as som et hing like SQL, but for Dom ain Model) would be useful against t he infrast ruct ure solut ions t hat have adapt er im plem ent at ions for NWorkspace. I can't let go of t he idea t hat what querying language you want t o use is as m uch of a lifest yle choice as is t he choice of program m ing language. Of course, it m ight depend even m ore on t he sit uat ion as t o which query language we want t o use, if we can choose. That 's a nice way t o end t his subj ect for now, I t hink ( m ore t o com e in Chapt er 8 and 9) . I t 's t im e t o m ove on.
Summary So we have discussed what Persist ence I gnorance is and som e ideas of how t o keep t he infrast ruct ure out of t he Dom ain Model classes, while at t he sam e t im e prepare for infrast ruct ure. We also discussed dat abase t est ing, and a lot of at t ent ion was spent on an idea of int roducing an abst ract ion layer. I n t he next chapt er, we will change focus t o t he core of t he Dom ain Model again, t his t im e focusing on rules. You will find t hat lot s of t he requirem ent s in t he list we set up in Chapt er 4 are about rules, so t his is a near and dear t opic when Dom ain Models are discussed.
Chapter 7. Let the Rules Rule I n t he last chapt er, we t ook a nice break and looked at som e infrast ruct ure preparat ions. Now we'll ret urn t o t he core m odel for a chapt er. This chapt er is about rules. The t opic of rules is a huge one. We will deal wit h part of it , focusing m ainly on validat ion rules. We will do it by addressing t he requirem ent s defined in Chapt er 4, " A New Default Archit ect ure," and add som e com m ent s where appropriat e. Looking back at t he requirem ent s list in Chapt er 4 ( t he list will be repeat ed short ly) , we can clearly see t hat t he m aj orit y of t he requirem ent s have som et hing t o do wit h rules. As I see it , t his is one area where Dom ain Models really shine. We can go quit e a long way wit hout needing a rules engine. As Evans says [ Evans DDD] , using several paradigm s at t he sam e t im e is problem at ic, so we are doing ourselves a favor if we can avoid m ixing Dom ain Models wit h rules engines. ( To give you a sense of how com plex it can be t o m ix paradigm s, t hat is why m ixing OO and Relat ional m odels is so t ricky.) I n m y last book [ Nilsson NED] , t here was a chapt er called " Business Rules" in which I provided a bunch of exam ple problem s relat ed t o where t o locat e t he rule evaluat ion. For som e reason, alm ost all t he exam ples ended up being checked in t he dat abase. Yet t hat was from m y dat abase era, and now I 'm a Dom ain Model guy, so you will find t hat t his chapt er is t ot ally different . I s t hat for t he bet t er? I definit ely t hink so, and I hope you will agree. OK, where shall we st art ? How about som e cat egorizat ion of rules?
Categorization of Rules I f we t ry t o cat egorize rules, we quickly find out t hat t here are m any dim ensions t o consider. For exam ple, t here are at t ribut es such as t he following: Where t o execut e ( in which t ier/ layer) When t o execut e Degree of com plexit y ( sim ple const raint s, spanning several fields, spanning several inst ances, and so on) St art ing act ions or not And so on, and so fort h I 'm sorry, but I won't be dealing wit h t his m uch ot her t han direct ing you t o som e ot her books on t he subj ect , such as von Halle's book [ von Halle BRA] , t he one by Ross [ Ross BRB] , and Halpin's [ Halpin I MRD] . You will find t hat t hese books are pret t y dat a- focused, but t hey are nevert heless highly int er est ing. Let 's t ry t o get st art ed from anot her angle. Let 's define som e principles and t hen get our hands dirt y.
Principles for Rules and Their Usage I t hink t he word principle is very good because it 's not st rict . I t 's m uch easier t o com e up wit h principles t han cat egories, probably because of t he built - in vagueness. St ill, principles are descript ive regarding int ent ion. That 's exact ly what I need here and now.
Two-Way Rules Checking: Optional (Possible) to Check Proactively, Mandatory (and Automatic) to Check Reactively You should be able t o check in advance ( proact ively) whet her t here will be a problem ( for exam ple, t rying t o persist t he current changes) so t hat you can t ake correct ive m easures. That way, t he program m ing m odel will be easier. And if you fail t o check in advance, t here is no m ercy, but you will learn ( react ively) t hat t here were problem s via an except ion.
All States, Even When in Error, Should be Savable Users hat e t o learn from applicat ions t hat t hey can't save t he current changes because of errors. A friend of m ine has com pared it t o not being allowed t o save a word processing docum ent if you have spelling errors. All is a " big" word, but we will t ry t o at least com e close. We should t ry t o avoid reaching t hose st at es t hat won't be persist able. I t 's also im port ant t o t hink about what error m eans here. An order m ight be considered t o be in error if it 's t oo large. But unt il t he user t ries t o subm it t he order it 's not really an error, j ust a sit uat ion he needs t o deal wit h before subm it t ing. And he should be able t o save t hat order before he subm it s it so he can cont inue working on it lat er on, t o solve t he problem and subm it it t hen.
Rules Should Be Productive to Use I t should be product ive t o work wit h rules, bot h when consum ing t he rules and when defining t he rules t hem selves. The product ivit y for consum ing rules will be addressed by m aking it possible t o fet ch m et adat a t o be used for set t ing up t he UI . That will cut t he t im es you find problem s when you check because t he UI has helped so t hat som e problem s j ust won't happen. ( Just wat ch out so you don't t rust t he UI t o be all t hat is needed regarding rules checking.) The product ivit y goal for defining rules m ight be reached wit h a sm all fram ework t hat let s you focus on t he int erest ing part s of t he rules, and t he boilerplat e code will be dealt wit h for you.
Rules Should Optionally be Configurable so that You Can Add Custom
Rules Som e rules are scenario dependent while ot hers are not , so it m ight be im port ant not only t o be able t o declare all rules direct ly in t he class definit ions. I nst ead, you need t o be able t o add cust om rules dynam ically for cert ain use cases, but also during deploym ent as configurat ion inform at ion. I t hink t hat t he configurat ion aspect is part icularly useful when you build product s t hat will be used by m any different cust om ers. I f you are building a syst em for a single cust om er, it 's probably of less value.
Rules Should Be Located with State Rules should at least be defined as closely as possible t o t he relevant Dom ain Model classes. By t his I don't m ean t he im plem ent at ion of t he rules t hem selves but t he declarat ions. We want encapsulat ion of t he usage so t hat we easily get t he " com plet e" pict ure of a Dom ain Model class by inspect ing it s class. I t hink t his principle helps anot her principle t hat could be called " Rules should be consist ent ." When t he codebase reaches a cert ain size, t here is a risk t hat t he rules m ay int erfere or even cont radict each ot her. Having t he rules t oget her and t oget her wit h t he st at e should help t o som e degree wit h m anaging t he rulebase.
Rules Should be Extremely Testable We will spend a lot of t im e working wit h and t est ing t he rules. For t his reason, it 's im port ant t o have a t est - friendly design for t he rules. I n realit y, adding rules also creat es problem s wit h t est ing. You need t o set up inst ances t o correct st at es. You can writ e t est helpers ( such as privat e m et hods in t he t est fixt ure) for dealing wit h t hat , but it 's st ill a hindrance.
The System Should Stop You from Getting into a Bad State A good way of sim plifying how t o work wit h rules is t o avoid t he need for t hem when possible. One way of doing t hat is, for exam ple, t o work wit h creat ion m et hods t hat will set up t he Dom ain Model obj ect s in a valid st at e t hat can never be m ade invalid.
Starting to Create an API We'll com e back t o t hese principles and evaluat e how we fulfilled t hem lat er in t he chapt er, but now it 's t im e t o com e back down t o Eart h and t ake up som e of t he problem s out lined in Chapt er 4. I 'm going t o st art wit h t he ext ernal API and see where we end up. I believe t hat will give a good, pragm at ic feeling about t he whole t hing. So t o rem ind you, t he problem s or request s out lined in Chapt er 4 were t he following:
1 . List cust om ers by applying a flexible and com plex filt er. 2 . List t he orders when looking at a specific cust om er. 3 . An order can have m any different lines. 4 . Concurrency conflict det ect ion is im port ant . 5 . A cust om er m ay not owe us m ore t han a cert ain am ount of m oney. 6 . An order m ay not have a t ot al value of m ore t han one m illion SEK. 7 . Each order and cust om er should have a unique and user- friendly num ber. 8 . Before a new cust om er is considered OK, his or her credit will be checked wit h a credit inst it ut e. 9 . An order m ust have a cust om er; an order line m ust have an order. 1 0 . Saving an order and it s lines should be aut om at ic. 1 1 . Orders have an accept ance st at us t hat is changed by t he user. Let 's t ake one of t he problem s; say, problem 6, " An order m ay not have a t ot al value of m ore t han one m illion SEK." How shall we approach t his problem ? There are several opt ions t o choose from . You could creat e som et hing like a sim ple rule- checking engine where you can send your inst ances t o be validat ed, or you could let t he inst ances t ake responsibilit y for t hat t hem selves. I like t he idea of let t ing t he inst ances t ake responsibilit y for t hem selves as m uch as possible ( or at least as far as is appropriat e) and t herefore prefer t he lat t er. Perhaps t his is a bit naïve, but also very sim ple in a posit ive way. I f I writ e a t est t o get st art ed, it could look like t his: [Test] public void CantExceedMaxAmountForOrder() { Order o = new Order(new Customer()); OrderLine ol = new OrderLine(new Product()); ol.NumberOfUnits = 2000000;
ol.Price = 1; o.AddOrderLine(ol); Assert.IsFalse(o.IsValid); }
What we did was t o add a propert y t o t he Order called IsValid. A pret t y com m on approach I t hink. There are also som e obvious problem s, such as t he following: What was t he problem ? We only know t hat t here was one, not what it was. We allow ed an incorrect t ransit ion. What if we forgot t o check? All we did was t o check if everyt hing was OK or not . We will com e back t o t he m ent ioned problem s, but we have som et hing m ore basic t o deal wit h first . A m ore im port ant problem is t his quest ion: What is it t he order is valid for ( or not valid for) ?
Context, Context, Context! Cont ext is, as always, very im port ant but pret t y m uch forgot t en in t he approach we st art ed out wit h. We need t o address t his before m oving on because it 's crucial t o t he rest of t he discussion. I t hink t he reason for t he com m on approach of IsValid as som e kind of general, cat ch- all t est m ight be because of t oo m uch focus on persist ence. I f we decouple persist ence from rules a bit and t ry t o adhere t o t he principle of let t ing all st at es be savable, we m ight end up wit h anot her approach. Then I t hink t he t ransit ions will be t he int erest ing t hing regarding rules. For exam ple, when an order is in t he st at e of NewOrder, m ore or less everyt hing m ight be allowed. But when t he order should t ransit ion t o t he Ordered st at e, t hat will only be allowed if cert ain rules are fulfilled. So saving an order t hat is in t he st at e of NewOrder should be possible even if t he order isn't valid for ent er Ordered st at e. Approaching rules t his way will focus rules on t he m odel m eaning and not let a t echnicalit y, such as persist ed or not , affect t he m odel. That said, t here is a realit y full of pract ical problem s, and we need t o deal wit h t hat well. So even if we have t he int ent ion of focusing on t he m odel, we have t o be good cit izens regarding t he infrast ruct ure also. Let 's see if t hinking about persist ed or not as a special st at e helps, but first I t hink it 's im port ant t o discuss what could give us problem s regarding rules if we don't t hink enough about t he infrast ruct ure.
Database Constraints Even t hough you m ight choose t o focus on put t ing all your rules in t he Dom ain Model, you will st ill probably end up wit h som e rules in t he dat abase. A t ypical reason for t his is efficiency. Som e rules are m ost efficient ly checked in t he dat abase; t hat 's j ust how it is. You can probably design around m ost of t hem , but t here will probably st ill be a couple of sit uat ions where t his is a fact of life. Anot her reason is t hat you m ight not be able t o design a new dat abase, or you m ight not be able t o design t he dat abase exact ly as you would like it , but you have t o adj ust t o a current dat abase
design. Yet anot her reason is t hat it 's oft en considered a good t hing t hat t he dat abase can t ake care of it self, especially if several different syst em s are using t he sam e dat abase. The im pedance m ism at ch bet ween obj ect - orient at ion and Relat ional dat abases t hat we discussed in Chapt er 1, " Values t o Value," will also show up here. For exam ple, t he st rings in t he dat abase will m ost likely have st at ic m ax lengt hs, such as VARCHAR(50). That a cert ain st ring isn't allowed t o be longer t han t hat is kind of a generic rule, but it 's m ost ly relat ed t o persist ence. The t ransit ion t o Persist ed st at e won't be allowed if t he st ring is t oo long. So it 's probable t hat you will have t o deal wit h rules whose invalidit y won't be det ect ed unt il t he st at e is persist ed t o t he dat abase. That in it s t urn represent s a couple of problem s. First , parsing error inform at ion is t roublesom e, especially when you consider t hat a t ypical O/ R Mapping solut ion oft en t ries t o be port able bet ween different dat abases. Or rat her, t he heart of t he problem is t hat t he O/ R Mapper probably won't be able t o do t he m apping for you when it com es t o errors and t ell you what field( s) caused t he problem in t he Dom ain Model; for exam ple, when t here's a duplicat e key error. Furt herm ore, t he error will com e at an inconvenient point in t im e. Your Dom ain Model has already been changed, and it will probably be very hard for you t o recover from t he problem wit hout st art ing all over again. I n fact , t he general rule is oft en t hat you should st art over inst ead of t rying t o do som et hing sm art . St ill, we do like t he idea of m aking it possible t o save our inst ances at any t im e. ( I n any case, we have set up som e preferences even if we can't follow t hem t o 100% . The ext rem e is oft en ext rem ely cost ly .) As I see it , t ry t o design for as m uch proact ivit y as possible ( or as appropriat e) so t hat t he num ber of except ions from t he dat abase will be ext rem ely few. For t hose t hat are st ill possible, be prepared t o deal wit h t hem one by one in code, or be prepared t o roll back t he t ransact ion, t oss out t he Dom ain Model changes, and st art all over again. A bit drast ic, but t hat 's t he way I see it . The key t o all t his is design, so you m ake t he problem as sm all as possible. OK, t hat was t he purely t echnical side of it ( t hat t he dat abase m ight show it s discom fort wit h us) . But it 's not j ust a m at t er of t hat t he rules are infrast ruct ure- relat ed only or dom ain- relat ed only. There are connect ions.
Bind Rules to Transitions Related to the Domain or the Infrastructure? What we have said so far is t hat we should t ry hard t o not ent er an invalid st at e. I f we follow t hat , we can always persist ( if we don't t hink about t he infrast ruct ure- relat ed const raint s for t he m om ent ) . That principle is t he ideal. I n realit y, it will be hard not t o break cert ain rules during a m ult iple st ep act ion. For exam ple, should you disallow t he change of t he nam e of a cust om er t o a nam e t hat t he rule t hinks is t oo short ? Well, perhaps, but it m ight j ust prevent t he user from doing what he want s t o do: nam ely delet ing t he old nam e and insert ing a new one inst ead. I t 's possible t o deal wit h t his by having a m et hod called Rename(string newName) t hat t akes care of t he work. During t he execut ion of t he m et hod, t he st at e of t he obj ect isn't correct , but we only consider it a problem before and aft er t he m et hod execut ion.
N ot e
This is in line wit h invariant s according t o Design by Cont ract [ Meyer OOSC] . Such invariant s are like assert ions t hat t he inst ance should abide by at all t im es, bot h before t he m et hod execut ion and aft er. The only except ion is during t he m et hod execut ion.
Anot her approach is t o sim ply not set t he propert y unt il you've finished wit h t he change. Let 's see if I can com e up wit h anot her exam ple. Assum e t hat t here are t wo fields, and eit her bot h field have t o be set or neit her of t hem . Again, a m et hod would solve t he problem , but if you scale up t his problem or consider t hat t he UI would prefer t o have t he propert ies exposed wit h set t ers, it 's clear t hat it 's hard not t o get int o an inconsist ent st at e t here from t im e t o t im e. Anot her quest ion m ight be if we should t rust t hat we didn't reach an invalid st at e? And if we against all odds did reach an invalid st at e ( a really invalid one, such as an erroneous change when t he st at e of t he order is Ordered) , should we allow t hat t o be st ored? I t m ight be t hat t he dat abase won't allow us t o have t hat prot ect ion for som e error sit uat ions. But even if you t ry hard t o let t he dat abase t ake care of it self regarding rules, you will probably let som e t hings slip t hrough, and if you have m oved t o Dom ain Model- focused way of building applicat ions, your dat abase is probably not as rigid when it com es t o self- prot ect ion. Aft er all, dom ain- focused rules t hat are som ewhat advanced are m ost oft en m uch easier t o express in t he Dom ain Model t han in a Relat ional dat abase, and you also want t o have t hem in t he Dom ain Model. The quest ion is: can we t rust t he " environm ent " ? I m ean, can we t rust t hat we won't persist a st at e t hat shouldn't be possible t o reach? For exam ple, can we expect t hat we don't have any bugs? Can we expect t hat t he consum er isn't creat ive and won't use reflect ion for set t ing som e fields t o excit ing values?
Refining the Principle: "All States, Even when in Error, Should Be Savable" Let 's see if we can form an exam ple of how I t hink about t he principle I called " All st at es, even when in error, should be savable." Assum e again an order t hat can be in t wo st at es, NewOrder and Ordered. We have only one dom ainrelat ed t ransit ion rule: t he order m ay not be t oo large t o ent er Ordered. That gives us t he valid com binat ions in Table 7- 1.
Ta ble 7 - 1 . Va lid Com bin a t ion s of Or de r St a t e s a n d Ru le Ou t com e s Or de r St a t e s
Ru le Ou t com e s
New Order
Not t oo large
New Order
Too large
Ordered
Not t oo large
So far, it feels bot h sim ple and exact ly what we want . Let 's add t he dim ension of persist ed or not , as shown in Table 7- 2.
Ta ble 7 - 2 . Va lid Com bin a t ion s of Or de r St a t e s a n d Ru le Ou t com e s Or de r St a t e s
Ru le Ou t com e s
Pe r sist e d or N ot
New Order
Not t oo large
Not persist ed
New Order
Not t oo large
Persist ed
New Order
Too large
Not persist ed
New Order
Too large
Persist ed
Ordered
Not t oo large
Not persist ed
Ordered
Not t oo large
Persist ed
First , I 'd like t o point out t hat we expect t hat t he t ransit ions t o Ordered com bined wit h Too large will not be possible. Even m ore im port ant , if for som e reason it does happen, it 's an exam ple of a st at e t hat is in real except ional error and t hat should not be savable.
N ot e Your m ileage m ight vary regarding if you will check t he dom ain- relat ed t ransit ion rules at persist t im e as well or not . I t m ight be considered a bit ext rem e in m any cases.
Requirements for a Basic Rules API Related to Persistence Now t hat we have decided on a specific cont ext for now, t hat of t he t ransit ion t o t he st at e of persist ed, let 's st art t he discussion about an API regarding t hat . We will get back t o ot her ( and from t he m odel's point of view, m ore int erest ing) st at e t ransit ion problem s, bot h general and specific, aft erward.
N ot e As usual, t he discussion here is really m ore about som e generic ideas t han t he API it self. I 'm not t rying t o t ell you how you should creat e your int er- faces/ API s, I 'm j ust t rying t o give m y t hought s on t he subj ect and hope t hat you m ay be prom pt ed t o find your own solut ions.
I 'd like t o t ake a new code exam ple where we apply t he idea about being proact ive regarding not persist ing except ional st at e errors and/ or problem s t hat will t hrow except ions from t he persist ence engine. I 'm going for t he lat t er: showing a sit uat ion when t he persist ence engine would t hrow an except ion. ( Again, t he form er problem should be hard t o get t o. I f everyt hing works as expect ed, t hat st at e shouldn't be possible t o reach.) Assum e t hat Order has Note, and Note is defined as VARCHAR(30) in t he dat abase. Depending on your solut ion for persist ence, you m ight get an except ion when t rying t o save a long st ring, or you will lose inform at ion silent ly. The im port ant t hing here is t hat I will proact ively cat ch t he problem . I t could look like t his: [Test] public void CantExceedStringLengthWhenPersisting() { Order o = new Order(new Customer()); o.Note = "0123456789012345678901234567890";
Assert.IsFalse(o.IsValidRegardingPersistence); }
So inst ead of asking if t he order is IsValid, I changed t he nam e of t he propert y t o IsValidRegardingPersistence t o m ake it very clear t hat t he order isn't valid in t he cont ext of being persist ed. Let 's set up a sim ple int erface for t he purpose of let t ing t he consum er ask an Aggregat e root if it 's in a valid st at e or not for being persist ed. I t could look like t his:
public interface IValidatableRegardingPersistence { bool IsValidRegardingPersistence {get;} }
So you let your classes t hat have persist ence- relat ed rules im plem ent t hat int erface. Norm ally t he consum er only has t o ask t he Aggregat e [ Evans DDD] root if it 's valid or not t o be persist ed and t he Aggregat e root will check it s children.
Externalize Rules á la Persistence? As you saw in t he code snippet , I chose t o let t he Dom ain Model classes t hem selves be responsible for checking rules; considering how we like t o deal wit h persist ence ( from t he " out side" ) , t hat m ight feel st range. This m ight be som et hing t o t hink about , but for now I 'll st ick t o m y first idea here, part ly because I t hink t he rules are at t he heart of t he Dom ain Model. The rules are not " j ust " an infrast ruct ural aspect , as persist ence is.
Good, we are back on t rack.
Back to the Found API Problems Several pages back I found out a couple of problem s wit h t he API I st art ed t o sket ch. I delayed t he discussion because I want ed t o first deal wit h t he problem of lack of cont ext . Wit h t hat out of t he way, let 's get back t o t hose problem s t o see if t hey st ill apply t o t he previous code snippet ( CantExceedStringLengthWhenPersisting() ) . The problem s were What was t he problem ? We only know t hat t here was one, not what it was. We allowed an incorrect t ransit ion. What if we forgot t o check? Let 's discuss t hose t hree problem s in t he cont ext of t he persist ence- relat ed rules.
What Was the Problem? The first problem st ill applies. I f t he inst ance isn't valid, it m ight well be int erest ing t o find out which rules have been broken. The Aggregat e root will aggregat e t he broken rules from all it s children as well as from it self and ret urn t he broken rules as an IList. Let 's add t o t he int erface for dealing wit h t his. I t could look like t his: public interface IValidatableRegardingPersistence {
bool IsValidRegardingPersistence {get;} IList BrokenRulesRegardingPersistence {get;} }
N ot e As I see it , t he list of broken rules is an im plem ent at ion of t he Not ificat ion pat t ern [ Fowler PoEAA2] . One im port ant t hing m ent ioned wit h t hat pat t ern is t hat it 's not only errors t hat we need t o be inform ed about , but warnings/ inform at ion as well. Anot her t hought t hat springs t o m ind is t hat t he broken rules list I have t alked about is pret t y focused on Aggregat es, but we can, of course, aggregat e ( pun not int ended) several such list s int o a single one in an Applicat ion layer [ Evans DDD] , such as from several Aggregat e root inst ances.
We Allowed an Incorrect Transition Sure, t he Note becam e t oo long, and t hat could be considered t o be a t ransit ion t o an invalid st at e. But I can't say I t hink of t hat t hing as very int erest ing m ost oft en. I would probably let t he order be around wit h a t oo- long Note for som e t im e t o m ake it possible for t he user t o delet e a few let t ers of t he Note. Unfort unat ely, in t his case, t he t oo- long Note won't be possible t o save. That t ransit ion bet ween not persist ed and persist ed, on t he ot her hand, is int erest ing here. But it won't be allowed. ( As I said, I will get an except ion or t he st ring will be t runcat ed silent ly.) The t ransit ion t o persist ed is a bit " special." We will get back t o t he problem of allowing incorrect t ransit ions in t he cont ext of dom ain- relat ed rules short ly.
What If We Forgot to Check? I f t he consum er doesn't follow t he prot ocol of first checking t he possibilit y of a t ransit ion before t rying t o m ake t he t ransit ion, we have an except ional sit uat ion, and except ional sit uat ions t hrow except ions. The sam e applies here, t oo. So if we t ried t o t ransit ion t o persist ed, we would like t o t hrow an except ion t hat is subclassing ApplicationException and will hold an IList of broken rules. Again, t he consum er isn't following t he prot ocol, and t hat 's an except ional sit uat ion. We got caught up in a specific t ransit ion problem : t ransit ion t o persist ed. I t 's t im e t o change focus from infrast ruct ure- relat ed t ransit ions t o dom ain- relat ed for a while, but we will t ry t o m ove som e of t he ideas wit h us, such as t hat it 's an except ion if t he consum er does not follow t he prot ocol.
Focus on Domain-Related Rules Let 's work wit h t he rem aining feat ure requirem ent s from Chapt er 4, one by one. We st art ed out wit h t he feat ure requirem ent num ber 6, which was called " An order m ay not have a t ot al value of m ore t han one m illion SEK," but we got int errupt ed when we realized t hat t he cont ext was m issing from t he sket ched API . Let 's see if we can t ransform t he t est a bit . I t looked like t his before: [Test] public void CantExceedMaxAmountForOrder() { Order o = new Order(new Customer()); OrderLine ol; ol = new OrderLine(new Product()); ol.NumberOfUnits = 2000000; ol.Price = 1; o.AddOrderLine(ol); Assert.IsFalse(o.IsValid); }
As you know by now, I t hink t here are several problem s wit h t his snippet . To follow t he ideas discussed so far, we could add a t ransit ion m et hod t o Ordered st at e; for exam ple, t he m et hod could be called OrderNow(). But I would st ill not be happy wit h t hat , because OrderNow() would m ost probably t hrow an except ion before we get a chance t o check. Again, I 'd like t o leave t he focus of what is persist ed and what is not for now. The int erest ing part here is t hat when we t ry t o t ransit ion t o Ordered, we can't do t hat . Let 's reflect t hat wit h som e changes t o t he t est . [Test] public void CantGoToOrderedStateWithExceededMaxAmount() { Order o = new Order(new Customer()); OrderLine ol = new OrderLine(new Product()); ol.NumberOfUnits = 2000000; ol.Price = 1; o.AddOrderLine(ol); try { o.OrderNow(); Assert.Fail(); } catch (ApplicationException ex) {} }
I t 's im port ant t o consider whet her when we are in t he Ordered st at e if we will t hen check in AddOrderLine() so t hat we aren't going t o break t he rule. I could let AddOrderLine() t ransit ion t he order back t o NewOrder , if AddOrderLine() would be allowed at all. That avoids t he problem . Anot her problem is t hat if one of t he OrderLines is changed when we are in Ordered, t hat m ight break t he rule. A t ypical solut ion in t his case would be t o work wit h im m ut able OrderLines inst ead so t hat t hey can't be changed. Let 's change t he t est t o reflect t hat . [Test] public void CantGoToOrderedStateWithExceededMaxAmount() { Order o = new Order(new Customer()); o.AddOrderLine(new OrderLine(new Product(), 2000000, 1)); try { o.OrderNow(); Assert.Fail(); } catch (ApplicationException ex) {} }
N ot e I could use t he ExpectedException at t ribut e of NUnit inst ead of t he TRy catch and Assert.Fail() const ruct ion. That would m ake t he t est short er. The only advant age of t he used const ruct ion is t hat I will check t hat t he expect ed except ion happens at t he OrderNow() call, and t hat is act ually m ost oft en a sm all advant age.
When an OrderLine has been added, it can't be changed any longer, and for t his sim ple exam ple, t he rule only has t o be checked in t he OrderNow() and AddOrderLine() m et hods now. Let 's m ove on wit h t he list .
Rules that Require Cooperation Feat ure num ber 5 was: " A cust om er m ay not owe us m ore t han a cert ain am ount of m oney." This rule sounds ext rem ely sim ple at first , but t he devil is in t he det ails. Let 's st art and see where we end up. An init ial t est could go like t his: [Test] public void CanHaveCustomerDependentMaxDebt() { Customer c = new Customer(); c.MaxAmountOfDebt = 10; Order o = new Order(c); o.AddOrderLine(new OrderLine(new Product(), 11, 1));
try { o.OrderNow(); Assert.Fail(); } catch (ApplicationException ex) {} }
However, t hat will probably not work at all. Why not ? Well, t he order needs t o be able t o find t he ot her orders of a cert ain st at e for t he cust om er at hand and what t ypically solves t hat is t he OrderRepository.
N ot e As you m ight recall, I chose t o go for a unidirect ional relat ionship bet ween Order and Customer classes. That 's why I ask t he OrderRepository for help ( as one of several possible st rat egies for sim ulat ing bidirect ionalit y) for going from Customer t o it s Orders.
We could deal wit h t hat by inj ect ing _orderRepository t o t he order in t he const ruct or like t his: Order o = new Order(c, _orderRepository);
But was t his a good solut ion? As usual, t hat depends on t he part icular applicat ion. Let t ing t he Order check t he current debt for t he Customer when it 's t im e for OrderNow() m ight be a bit lat e regarding t he usabilit y aspect for t he user. Perhaps a bet t er solut ion would be t o ask what t he m axim um am ount of a new order can be before creat ing t he order. Then t he user creat es t he new order, and t he user can get feedback in t he UI when t he lim it is reached. This doesn't change t he API we j ust saw, but it is m ore of a convenience. A variat ion t o t he locat ion of responsibilit y would be t o ask t he Customer about IsOrderOK(orderToBeChecked). I n a way, it feels nat ural t o let t he Customer be responsible for deciding t hat because t he Customer has t he dat a t hat is needed t o m ake t he j udgm ent . I f we t hink about t he dom ain and t ry t o code in t he spirit of t he dom ain, is it likely t hat we give ( or should give) t he cust om er t he responsibilit y of checking t he orders? Perhaps it is if we st at e t he quest ion a bit different ly, saying t hat we should ask t he cust om er t o accept t he order. As a m at t er of fact , t hat 's pret t y sim ilar t o what we coded previously except t hat OrderNow() was locat ed on t he Order. That m ight be an indicat ion of som et hing. Anot her problem wit h t he solut ion shown in t he code snippet is t hat t he _orderRepository is inj ect ed in t he order inst ance. I f you reconst it ut e an order from persist ence and you do t hat by using an O/ R Mapper, you're not in charge of t he creat ion of t he order inst ance. You could inj ect t he _orderRepository in t he Reposit ory during reconst it ut ion, t hough, but it feels a lit t le bit like a hack ( consider, for exam ple, when you read a list of orders and t hen you have t o inj ect t he Reposit ory int o each inst ance of t he list ) . Aft er having t hought about it , let 's t ry t o keep t he responsibilit y in t he order for checking it self, but provide t he Reposit ory via a set t er inst ead or at t he t im e of t he check as a param et er t o t he OrderNow() m et hod ( but t hat st rat egy has a t endency of not scaling well) .
N ot e I t hink t his discussion point s out a weakness in at least som e OR Mappers: t hat you don't cont rol t he inst ance creat ion during reconst it ut ion as m uch as you would like, especially considering t he growing popularit y of Dependency I nj ect ion fram eworks, as we will discuss in Chapt er 10, " Design Techniques t o Em brace."
Locating Set-Based Processing Methods No m at t er which of t he st yles previously discussed you prefer, you m ight t hink a m inut e or t wo about how t o check t he current debt . Of course, you could writ e code like t his ( assum ing t hat we use orders and not invoices for det erm ining t he current debt , which is probably a bit t wist ed) : //Order, code for checking current debt for OrderNow() decimal orderSum = 0; IList orders = _orderRepository.GetOrders(c); foreach (Order o in orders) { if (_HasInterestingStateRegardingDebt(o)) orderSum += o.TotalValue; }
I t hink t hat code is a no- no! A bet t er solut ion would be t o expose a m et hod on t he OrderRepository wit h a m et hod called som et hing like CurrentDebtForCustomer(), t aking a Customer inst ance as param et er.
N ot e Wit h Reposit ory int erface design, it 's oft en a good idea t o provide overloads, such as X(Customer) and X(Guid). I t is annoying having t o inst ant iat e a Customer j ust t o be able t o call t he m et hod if you hold on t o t he I D of t he Customer .
I know what t he dat abase guys are t hinking right now. " What about real- t im e consist ency? Will t he Reposit ory m et hod execut e wit h t he t ransact ion isolat ion level of serializable in an explicit t ransact ion during save at least ?" Well, it is possible, but t here are problem s. One problem is t hat t he O/ R Mapper you are using ( if we assum e for t he m om ent t hat an O/ R Mapper is being usedm ore about t hat in lat er chapt ers) m ight not allow you t o t ake det ailed cont rol of t he persist process regarding validat ion, locking, and so on. Anot her problem is t hat t he nat ure of t he persist process m eans it m ight t ake som e t im e if you have lot s of dirt y inst ances t o be persist ed, so t he lock t im e for all orders for t he cust om er m ight be pret t y long. I t hink t hat in t his case it 's usually OK not t o expect real- t im e consist ency here, only som et hing pret t y close t o real- t im e consist ency. I m ean, check t he sum of t he ot her orders j ust before saving t his order, but do not span t he read wit h a high isolat ion level or not even do t he check wit hin t he sam e t ransact ion.
I t hink t hat goes pret t y m uch hand in hand wit h what Evans says regarding Aggregat es [ Evans DDD] . He says som et hing like st rive for real- t im e consist ency wit hin an Aggregat e, but not bet ween Aggregat es. Consist ency problem s bet ween Aggregat es can be dealt wit h a lit t le lat er. So if you find t he approach accept able ( again, we are t alking about a pret t y low risk here, for a t ypical syst em at least ) , but you would like t o det ect t he problem , you could creat e som e bat ch program t hat kicks in every now and t hen and checks for problem s like t his and report s back. Or why not put a request on a queue at persist t im e t hat checks for problem s like t his regarding t he part icular cust om er? I t 's such an unlikely problem , so why should any user have t o wait synchronously for it t o be checked? The fact t hat I t hink t he m et hod is at om ic signals t hat we should use a Service [ Evans DDD] inst ead t o m ake t he whole t hing m ore explicit . The Service could check t he current debt and t herefore be used proact ively by t he Order at good places in t im e. I t could also be used pret t y m uch react ively at save t im e, and som e sort of request could be put on a queue and be used react ively aft er save. The Service would probably use t he sam e Reposit ory m et hod as was j ust discussed, but t hat 's not anyt hing t he caller needs t o know, of course. What should it do react ively aft er t he save? Perhaps m ove t he order t o one of t wo st at es so t hat when t he user st ores an order, it goes int o st at e NewOrder. Then t he Service kicks in and m oves t he order t o st at e Accept ed or Denied, as shown in Figure 7- 1 .
Figu r e 7 - 1 . St a t e gr a ph
Aft er all t his ram bling I t hink we have t hree decent solut ions t o t he problem : one wit h pret t y t ight coupling ( everyt hing being done in real t im e, possibly wit h high t ransact ion isolat ion level) , one wit h less coupling ( but t hat is perhaps a bit m ore com plex because t here are m ore " m oving part s" ) , and finally one where we don't do anyt hing ext ra beyond what was shown in t he t est ( but wit h a sm all risk of inconsist ency t hat we will have t o t rack delayed) . Would one of t hese suit you? OK, we t ouched upon using a Service for helping us out wit h a cost ly processing problem . Services in t he Dom ain Model oft en arise from anot her necessit y as well.
Service-Serviced Validation Com plet ely by coincidence, we do need such a rule t hat is by it s very nat ure service- based. I 'm t hinking about feat ure 8, " Before a new cust om er is considered OK, his or her credit will be checked wit h a credit inst it ut e."
We could very well call t hat credit inst it ut e service in t he Grant() m et hod, or we could use a solut ion sim ilar t o t he one I j ust sket ched, let t ing our Service kick in aft er persist , t aking care of bot h calling t he ext ernal service and m oving t he cust om er t o one of t wo st at es. One problem wit h t his approach is t hat I 'm binding dom ain rules a bit t oo m uch t o persist ence. Anot her big problem wit h t his approach of m oving som e funct ionalit y out of t he sandbox of t he current Unit of Work/ I dent it y Map is t hat what you m ight have in your local cache will be out of sync wit h t he dat abase because processing t akes place in t he aut onom ous Service. Of course, t hat 's t he risk you always have wit h caching, if t he cache could be bypassed. I f you know t hat t he cache will be invalidat ed because of an explicit Service call in real t im e, it 's pret t y easy. Just kill t he cache. I t m ight be expensive t o recreat e it , t hough, or perhaps you even know exact ly what needs t o be refreshed. On t he ot her hand, if you don't know when t he operat ion t hat invalidat es t he cache will happen, you have t o t ake ext ra care and refresh t he cache as oft en as possible. As I see it , all t his hint s at being aggressive wit h your cache m anagem ent and not let t ing t he cache get t oo big and live for a long t im e, at least not for t he dat a t hat changes quit e frequent ly.
N ot e When I t alk about cache here, I 'm m ost ly t hinking about t he I dent it y Map [ Fowler PoEAA] .
Let 's focus som e m ore on t ransit ions.
Trying to Transition when We Shouldn't I used different st at es for increasing t he fulfillm ent of t he " always savable" principle before. But a " crit ical" passage is when t he user want s t o m ake a t ransit ion in t he st at e graph. Sure, we could deal wit h t his in a sim ilar way t o what we discussed before so t hat we m ove t o a t em porary st at e and t hen a Service t akes responsibilit y for m oving t o t he real st at e or t o a failure st at e. I guess t here are sit uat ions where t his m akes sense, but for feat ure 11, " Orders have an accept ance st at us t hat is changed by t he user," it m ight j ust feel ridiculous, depending on what t he cont ext is.
N ot e Did you not ice how it all cam e t oget her? I t alked about OrderNow() before, and t hat m ight have seem ed t o be kind of a t echnical solut ion t o t he problem of " always savable," but it was core t o t he requirem ent s. And according t o t he requirem ent s, Accept() m ight m ake m ore sense.
OK, " ridiculous" is t oo st rong a word, but if we assum e t hat t he rules for checking are j ust a couple of sim ple const raint s wit hout t he cross Aggregat e border problem s, I would prefer t o apply t he principle discussed earlier: t hat is, let t ing t he user m ake a proact ive check, and if t he result is posit ive, t he st at e- changing m et hod will execut e successfully. A pict ure, I m ean code, says m ore t han a t housand words:
[Test] public void CanMakeStateTransitionSafely() { Order o = new Order(new Customer()); Assert.IsTrue(o.IsOKToAccept); //We can now safely do this without getting an exception... o.Accept(); }
Checking t he IsOKToAccept propert y is opt ional, but not doing it and t hen calling Accept() at a t im e when we shouldn't is except ional, and we will get an except ion. Except ions are for except ional problem s; for " expect ed problem s" bool is fine. We oft en don't j ust wonder if it will be OK or not , but we also want t o get a list of reasons why it 's not OK. I n t hese cases, t he proact ive m et hod could ret urn a list of broken rules inst ead, som et hing like t his: [Test] public void CanMakeStateTransitionSafely() { Order o = new Order(new Customer()); Assert.AreEqual(0, o.BrokenRulesIfAccept.Count); //We can now safely do this without getting an exception... o.Accept(); }
I prefer t o do t his on a case- by- case basis. There is no st andard int erface or anyt hing t o im plem ent , j ust a principle for int erface design. So t his is act ually j ust a prot ocol for m aking it possible t o be proact ive regarding broken rules det ect ion.
The Transaction Abstraction The t ransact ion abst ract ion is a powerful one, and one t hat is underut ilized. An alt ernat ive solut ion t o t he t ransit ion problem would be t o use t he t ransact ion abst ract ion for t he Dom ain Model and validat ions as well. Then during t he t ransact ion, it wouldn't m at t er if t he st at e wasn't correct ; only before and aft er would m at t er. For exam ple, t he API could t hen look like t his: //Some consumer... DMTransaction dMTx = Something.StartTransaction(); c.Name = string.Empty; c.Name = "Volvo"; dMTx.Commit();
I n t he code snippet , t he rules weren't checked unt il Commit() . As you underst and, t his opens up a can of worm s t hat needs t o be dealt wit h, but it m ight be an int erest ing solut ion for t he fut ure.
The solut ion becom es even m ore int erest ing if you consider t he new System.Transaction nam espace of .NET 2.0. I n t he fut ure, we m ight get t ransact ional list classes, hash t ables, and so on. I t get s even m ore int erest ing considering t ransact ions spanning bot h t he Dom ain Model and t he dat abase. Again, loads of problem s, but int erest ing.
Obviously t here are lot s of variat ions t o t he problem of st at e t ransit ions. One such problem is deciding when an act ion should be t aken as well. St ay t uned.
Business ID The exam ple of a st at e t ransit ion t hat is paired wit h an act ion is feat ure 7 on t he feat ure list , " Each order and cust om er should have a unique and user- friendly num ber." A spont aneous solut ion t o t he problem is let t ing t he dat abase t ake care of t he problem wit h a built - in m echanism , such as I DENTI TY does for MS SQL Server. But t here are problem s wit h t his approach. First of all, you will have processing out side of your Dom ain Model, which com es at a cost . You will need t o force a refresh of t he affect ed inst ance, which is not t oo hard but is som et hing t o keep t rack of and m ight get a bit com plex. Anot her problem is t hat t he value m ight com e t oo early, long before t he user has decided t o m ove on wit h t he Order, when he st ill j ust considers it t o be in " perhaps" st at e. ( Obviously, t here are solut ions t o t hat as well.) Yet anot her problem is t hat O/ R Mappers will oft en cause you problem s if you also use t he IDENTITY as a prim ary key. The sem ant ics for using a Guid as t he prim ary key, which requires less coupling t o t he dat abase, are oft en a bit different .
N ot e I n no way am I saying t hat you m ust use t he I DENTI TY ( as t he aut o increasing propert y is called in SQL Server) colum n as a prim ary key j ust because you have such a colum n. You can let it be an alt ernat e key only, but you m ight get t em pt ed from t im e t o t im e.
So what are t he alt ernat ives? There are several of t hem . Probably t he easiest and m ost direct one is t o let t he Reposit ory have a m et hod for grabbing t he next free Id in som e way. I t could look som et hing like t his ( assum ing t hat an Order shouldn't get an OrderNumber unt il it 's accept ed) : //Order public void Accept() { if (_status != OrderStatus.NewOrder) throw new ApplicationException ("You can only call Accept() for NewOrder orders."); _status = OrderStatus.Accepted; _orderNumber = _orderRepository.GetNextOrderNumber(); }
N ot e I nst ead of ApplicationException ( or a subclass) , it m ight be a good idea t o use InvalidOperationException or a derivat ive.
Yet t his opens up a whole can of worm s, at least in syst em s t hat are som ewhat st ressed from t im e t o t im e or if t he OrderNumber series is not allowed t o have holes in it . I t m eans t hat t he GetNextOrderNumber() won't writ e changes t o t he dat abase, or at least it will not com m it t hem . This m eans t hat you need an expensive lock wrapping t he Accept() call and t he following persist m et hod. Yet anot her approach is t o use what will probably be t he next value and t hen be prepared for duplicat es if som eone else already has used it . I f you are prepared t o cat ch except ions during t he persist m et hod because of duplicat e key, what do you do t hen? Well, you could call Accept() again, but t hen Accept() will t hrow an except ion t he way it is current ly writ t en. Perhaps it 's bet t er t o j ust ask for a new OrderNumber in t he code t rying t o recover from t he duplicat e key except ion. That in it s t urn creat es anot her set of problem s, such as t alking t o t he dat abase during t he persist m et hod or det ect ing which inst ance has t he problem in t he first place ( and for which colum n if t here are several candidat e keys for t he inst ance) . All t hese problem s can be avoided if you accept holes in t he num ber series and if t he m et hod for grabbing t he next num ber also com m it s t he current m ax in a way sim ilar t o how I DENTI TY works in SQL Server. I m ean, I DENTI TY allows holes, and it will wast e a value if you do a ROLLBACK. To m ake it even clearer t hat t he GetNextOrderNumber() act ually m akes inst ant changes t o t he dat abase and doesn't wait for t he next call t o t he persist m et hod, I t hink we should use a Service inst ead. My Reposit ories don't m ake decisions for t hem selves when doing a com m it and such, but Services do because Services should be aut onom ous and at om ic. So wit h t hese changes in place, t he Accept() m et hod could now look like t his ( assum ing t hat an IOrderNumberService has been inj ect ed t o _orderNumberService in t he order before t he call) : //Order public void Accept() { if (_status != OrderStatus.NewOrder) throw new ApplicationException ("You can only call Accept() for NewOrder orders"); _status = OrderStatus.Accepted; _orderNumber = _orderNumberService.GetNextOrderNumber(); }
Now t he risk of problem s during t he persist m et hod is reduced so m uch t hat it 's except ional if one arises. You'd be right if you t hink I avoided t he problem by accept ing holes in series ( which was act ually even st at ed as OK wit h t he defined requirem ent s) , but som et im es t hat j ust isn't possible. I st rongly dislike such series, but t here are st ill sit uat ions where you can't avoid t hem . Then what ? Well, I
guess I would go back t o square one. I f t hat wasn't enough, I would probably t urn t he whole t ransit ion m et hod int o a Service inst ead, m oving t he order t o Accept ed st at us and set t ing an ordernumber. This is not as pure or clean as doing t he work in t he Dom ain Model, but when realit y calls, we should respond. OK, so far we have discussed quit e a lot of different problem s t hat we have t o deal wit h when it com es t o rules in t he Dom ain Model. There is one m ore t o look at before we m ove on.
Avoiding Problems Feat ure 9, " An order m ust have a cust om er; an order line m ust have an order," is a great exam ple of a rule t hat I 'd like t o not have as a rule at all if possible, but I 'd rat her force it in t he const ruct or inst ead. Then t here's no need t o check for it . Clean, sim ple, efficient . Just good. I know, I know, t his set s your alarm bells ringing. Of course t here are problem s, t oo. For exam ple, it 's possible t hat you'd like t o have an inst ance of an order wit hout first having t o decide on t he cust om er because of t he UI design.
N ot e This can be dealt wit h in t he UI , of course, in several different ways. St ill, it m ight not feel good t o creat e a " problem " if we don't have t o.
Anot her problem is t hat t he creat ion code m ight get so com plex t hat it can t hrow except ions it self, and t hen we are back int o t he problem s again wit h how t o expose broken rules, and so on. We also said t hat except ions should be except ional, so it j ust doesn't feel right t o t oss except ions in t he creat ion code for saying t hat t here was a broken rule. There is one t hing t hat eases t he burden som ewhat . We could let t he feat ure request t alk about persist ent inst ances and not t ransient inst ances. I 'm t hinking about OrderLine, for exam ple. I n m y current design, OrderLine isn't an Aggregat e on it s own, but is one part of t he Order Aggregat e inst ead. Therefore, t he only way t o m ake an OrderLine persist ent is t o associat e it t o an Order wit h AddOrderLine(). Unt il t hen, I probably don't care t hat t he OrderLine doesn't have an Order. The OrderLine can't be st ored, and t he persist m et hod won't t hrow an except ion eit her because of an or phan OrderLine ( because t he persist m et hod won't know about it at all) . Sure, it m ight be t he case t hat som e processing in t he OrderLine requires knowledge of t he Order, but if not , I t hink t his is a fine solut ion.
N ot e And right now I t hink it 's arguable whet her t he OrderLine needs t he knowledge of it s Order at all. We can probably m ost oft en avoid t hat bidirect ionalit y t hat we will have ot herwise.
I don't know if you've not iced it , but I com e back t o one of t he DDD- pat t erns, t he Aggregat e, over and over again. I 'd like t o t ake a break and t alk about t hat before m oving on.
Aggregates as the Tool Again The unit for checking rules is... Aggregat es! I have found Aggregat e design t o be ext rem ely im port ant , and it is som et hing I spend quit e a lot of t im e on in m y current design work. I briefly t alked about rules aggregat ion earlier in t his chapt er, but let 's reit erat e and delve a lit t le bit deeper.
Aggregate Roots Aggregate Rules When t he consum er of an Aggregat e root asks for broken rules regarding persist able or not , he should receive broken rules from t he com plet e Aggregat e and not only t he root inst ance. The aggregat ion doesn't necessarily have t o use t he sam e IValidatableRegardingPersistence int erface for t he children classes; I act ually prefer it not t o because t hat will dist urb m y st rat egy for react ively checking for validit y at persist . ( I don't m ean dist urb as in creat ing severe problem s, but m ore as in duplicat ing execut ion cycles for no reason.) Also, t his way t he Aggregat e root has t o decide if t he ordinary rules of t he children should be used or not . Cont ext is king.
An Obvious Aggregate-Level Rule: Versioning I 'd like t o end t his short sect ion about Aggregat es wit h j ust a few words about feat ure 4. Concurrency conflict det ect ion is im port ant . That feat ure could be t hought about as a kind of business rule, but I 'd like t o t hink about it as a t echnical problem m ore t han a business problem . I t 's also t he case t hat m ost O/ R Mappers have at least som e support for concurrency conflict det ect ion, but I 'll leave t alking about t hat here and post pone it unt il Chapt er 8, " I nfrast ruct ure for Persist ence," and Chapt er 9, " Put t ing NHibernat e int o Act ion." Let 's change focus a lit t le bit . Before we get int o discussing t he im plem ent at ion of t he API , let 's first see if we should add anyt hing m ore.
Extending the API We have t alked about t he rule API regarding persist ence- relat ed rules and how t o get inform at ion about problem s wit h t hose. What m ore do we need of t he API ? Well, depending on t he applicat ion we are building, we m ight really need loads of ot her rules- relat ed t hings. I nspired by t he principles I st art ed t he chapt er wit h, one t hing t hat springs t o m ind is how nice it would be t o be able t o check for rules very proact ively, especially when in t he UI . Anot her im port ant t hing is t o be able t o add cust om ized rules, not only at com pile t im e but also at deploym ent t im e. Let 's st art wit h a look at consum ing t he rules in t he UI .
Ask for Rules to Be Used to Set Up UI I m ent ioned [ Nilsson NED] t hat I want ed t o reuse m y rules from t he logic layers in t he user int erface. Not duplicat e, but reuse. Now is t he t im e. The approach I 'm going t o use here is t o let t he classes expose m et adat a t hat can be used by t he UI if it want s t o. A sim ilar approach is t he one t aken wit h Web Services t hat expose an XSD Schem a, providing requirem ent s on t he dat a. I 'm going for a slight ly different approach t o st art off, providing list s of inst ances wit h sim ilar inform at ion.
N ot e Mart in Fowler describes t he problem in som e det ail [ Fowler Fixed- Lengt hSt ring] . He proposes a slight ly different approach where an obj ect for a st ring knows t he m axim um lengt h on it s own, for exam ple.
Make It Possible to Inject Rules I t would be nice t o be able t o define rules on a case- by- case basis because som e rules will obviously be very dependent on cont ext . Frequent ly, you want t he cust om er t o be able t o add his own specific rules, and let t ing him do t hat isn't t oo hard if you creat e and expose a sim ple language for t he cust om er t o use t o add rules.
Refining the Implementation So far we have been focusing on t he rules API from a consum er viewpoint . I t hink it would be int erest ing t o writ e about som e ideas on how t o im plem ent t he rules as well. So let 's get under t he hood of t he Order class and t ry out som e ideas. Before we act ually st art " refining" we need t o com e up wit h a very sim ple solut ion t o t he problem so t hat we have som et hing t o st art wit h.
A Naïve Implementation Let 's see...first of all, we need a t est . We can reuse som et hing from before and change it a bit . The CantExceedStringLengthWhenPersisting() t est will do. I t looked like t his: [Test] public void CantExceedStringLengthWhenPersisting() { Order o = new Order(new Customer()); o.Note = "0123456789012345678901234567890";
Assert.IsFalse(o.IsValidRegardingPersistence); }
We need t o writ e an IsValidRegardingPersistence propert y on t he Order class for t he t est even t o com pile. Som et hing like t his should work: //Order public bool IsValidRegardingPersistence { get { if (Note.Length > 30) return false; return true; } }
N ot e Of course, I haven't forgot t en t he TDD m ant ra of red, green, refact or, red, green, refact or. I j ust didn't want t o slow down your reading rhyt hm here.
OK, t hat works. Let 's t ake a copy of t he t est ( clipboard inherit ance) , change t he nam e, and expand it a bit : [Test] public void TryingIdeasWithTheRulesAPI() { Order o = new Order(new Customer()); o.Note = "012345678901234567890123456789"; Assert.IsTrue(o.IsValidRegardingPersistence); o.OrderDate = DateTime.Today.AddDays(1); Assert.IsFalse(o.IsValidRegardingPersistence); o.OrderDate = DateTime.Today; Assert.IsTrue(o.IsValidRegardingPersistence); o.Note += "012345"; Assert.IsFalse(o.IsValidRegardingPersistence); }
As you saw, I added one m ore rule st at ing t hat t he OrderDate can't be a fut ure dat e, and for som e reason ( not t oo m uch of a st ret ch) t hat rule was m andat ory regarding persist ence.
N ot e To m ake t he OrderDate rule obviously and m andat ory connect ed t o infrast ruct ure, it shouldn't be allowed t o be sm aller/ larger t han what your dat abase can st ore in a DATETIME ( if t here is such a difference for your dat abase) .
I n order t o get a green t est , we need t o change IsValidRegardingPersistence a lit t le bit , as follows: //Order public bool IsValidRegardingPersistence { get { if (Note.Length > 30) return false; if (OrderDate > DateTime.Today) return false; return true; } }
I t hink t hat will do for now as far as rules com plexit y goes. Not t oo m uch, eh? The next st ep is t o report back t o t he user, so we need t o writ e a BrokenRulesRegardingPersistence
propert y. Again, let 's do som et hing ext rem ely basic, but first we need t o m ake a few changes t o t he t est : [Test] public void TryingIdeasWithTheRulesAPI() { Order o = new Order(new Customer()); o.Note = "012345678901234567890123456789"; Assert.IsTrue(o.IsValidRegardingPersistence); Assert.AreEqual(0, o.BrokenRulesRegardingPersistence.Count); o.OrderDate = DateTime.Today.AddDays(1); Assert.IsFalse(o.IsValidRegardingPersistence); Assert.AreEqual(1, o.BrokenRulesRegardingPersistence.Count); o.OrderDate = DateTime.Today; Assert.IsTrue(o.IsValidRegardingPersistence); Assert.AreEqual(0, o.BrokenRulesRegardingPersistence.Count); o.Note += "012345"; Assert.IsFalse(o.IsValidRegardingPersistence); Assert.AreEqual(1, o.BrokenRulesRegardingPersistence.Count); }
And t hen a first im plem ent at ion: //Order public IList BrokenRulesRegardingPersistence { get { IList brokenRules = new ArrayList(); if (Note.Length > 30) brokenRules.Add("Note is too long."); if (OrderDate > DateTime.Today) brokenRules.Add("OrderDate is in the future."); return brokenRules; } }
This is oft en not det ailed enough inform at ion about broken rules, but at least we have st art ed. What feels worse right now is t hat I don't want t o express t he sam e rules in bot h t he IsValidRegardingPersistence and t he BrokenRulesRegardingPersistence propert ies, so for now let 's change IsValidRegardingPersistence t o an execut ion inefficient solut ion inst ead, which cut s down on duplicat ion.
N ot e This inefficiency regarding execut ion is oft en not m uch t o worry about if you put it in
relat ionship t o som e dat abase calls, for exam ple.
//Order public bool IsValidRegardingPersistence { get {return BrokenRulesRegardingPersistence.Count == 0;} }
OK, we have dealt wit h t he possibilit y of asking whet her t he inst ance is in a valid st at e for being persist ed at any t im e and what t he current problem s are ( " dealt wit h" is an overst at em ent , but st ill) . There is anot her prot ocol, t hough, and t hat relat es t o t ransit ions. Let 's deal wit h a t ransit ion and m ake it possible t o get inform at ion about t he problem s. We will also deal wit h t he sit uat ions react ively ( t hat is, we'll react if t he t ransit ion is at t em pt ed when t here are known problem s) . Let 's say t hat a rule for being able t o Accept() an order is t hat t he Customer for t he order m ust be in Accepted st at e. I t could go like t his: [Test] public void TryingTheAcceptTransitionWithTheRulesAPI() { Order o = new Order(new Customer()); Assert.IsFalse(o.IsOKToAccept); o.Customer.Accept(); Assert.IsTrue(o.IsOKToAccept); }
You should not e especially t hat for a new Order (NewOrder st at e) , it 's okay t o have a Customer t hat isn't accept ed. Then if we m ake t he Customer Accepted, it 's okay t o Accept() t he Order as well. One m ore t hing I 'd like t o show, t hough, is t hat t he general rules for t he Order will be applied at t he t ransit ions as well, so let 's add t hat t o t he t est : [Test] public void TryingTheAcceptTransitionWithTheRulesAPI() { Order o = new Order(new Customer()); Assert.IsFalse(o.IsOKToAccept); o.Customer.Accept(); Assert.IsTrue(o.IsOKToAccept); o.OrderDate = DateTime.Today.AddDays(1); Assert.IsFalse(o.IsOKToAccept); try { o.Accept(); Assert.Fail(); } catch (ApplicationException ex) {}
}
N ot e What also happens is t hat t he new Order as default for OrderDate is set t o DateTime.Today in t he const ruct or. That 's why it is IsOKToAccept direct ly.
And while I was at it , I also showed what will happen if t he t ransit ion is t ried even t hough it shouldn't work. We need t o do som et hing in t he Order for t his. First , let 's do som et hing in IsOKToAccept. //Order public bool IsOKToAccept { get { if (Customer.Status != CustomerStatus.Accepted) return false; return IsValidRegardingPersistence; } }
As you saw, I first checked t he specific rules ( or rule in t his case) and t hen t he persist ence relat ed rules, because I don't want t o m ove on wit h som et hing t hat won't be savable; t herefore, I want t o point out problem s ( persist ence relat ed ones as well) as early as possible. Again, I need t o change t his a bit if I also want a m et hod for t elling what broken rules t here will be if I call t he Accept() , but t hat 's sim ple and not im port ant t o show right now. The sam e goes for t he Accept() m et hod. I t 's pret t y sim ple and j ust needs t o check IsOKToAccept before doing t he t ransit ion. What m ight be a bit " t ricky," or at least not obvious, is t hat now t he rules for t he IsValidRegardingPersistence j ust got a bit m ore com plex, because aft er we ent er t he Accepted st at us of t he Order, we will have t o check t he sam e rules even in t he IsValidRegardingPersistence propert y. We m oved t he real processing t o t he BrokenRulesRegardingPersistence propert y as you saw before, so t hat is where you will see t he change: //Order public IList BrokenRulesRegardingPersistence { get { IList brokenRules = new ArrayList(); if (Note.Length > 30) brokenRules.Add("Note is too long."); if (_orderDate > DateTime.Today)
brokenRules.Add("OrderDate is in the future."); if (_OrderIsInThisStateOrBeyond(OrderStatus.Accepted)) _CollectBrokenRulesRegardingAccepted(brokenRules); return brokenRules; } }
So in _CollectBrokenRulesRegardingAccepted() , t here will be a specific rule evaluat ion for ent ering ( or rat her, in t his case, being in) t he Accepted st at e.
N ot e You m ay have not iced t hat I used t he Collect ing Param et er pat t ern [ Beck SBPP] for _CollectBrokenRulesRegardingAccepted() . I sent in t he list of broken rules and asked t he m et hod t o add m ore broken rules t o t he list if appropriat e.
To sum m arize all t his, I t hink you recognize plent y of sm ells and lack of funct ionalit y. For exam ple: Code explosion in BrokenRulesRegardingPersistence because rule evaluat ion is m anually coded Ret urned inform at ion about broken rules is j ust strings, which isn't enough We haven't addressed cust om izat ion We haven't addressed providing inform at ion about t he rules t hem selves t o be used by t he UI I t 's t im e t o refine t his naïve im plem ent at ion a lit t le bit , wit hout showing t he it erat ions for get t ing t here. Let 's reuse t he t est s and generalize t he im plem ent at ion a bit .
Creating Rule ClassesLeaving the Most Naïve Stage I t hink creat ing rule classes for t he basic needs so t hat t hey can be used declarat ively will cut down on code explosion, m anual rule evaluat ion, and deal wit h cust om izat ion and providing inform at ion about t he rules t o t he out side. The com plex rules always have t o be coded by hand for each sit uat ion and are hard t o cust om ize by adding inform at ion in a config file. Furt herm ore, it 's not im port ant t o provide inform at ion about t he com plex rules t o generic form s. Those rules aren't m y t arget here. They aren't unint erest ing; on t he cont rary, I want t o code t hem by hand. I f t hey fit int o t he big pict ure, t hat 's fine, but what I 'm t rying t o say is it 's not im port ant t hat t hey are declared. My t arget for what should be declared is t he sim ple rules: t hose t hat are unint erest ing t o writ e by hand over and over again. Let 's give it a t ry.
N ot e As always, wat ch out so you don't fall int o t he t rap of building fram eworks up front . I t 's generally bet t er t o let t hem show t hem selves in your code and t hen you go from t here. [ Fowler Harvest ed Fram ework]
My rule classes should im plem ent an int erface; let 's call it IRule: public interface IRule { bool IsValid {get;} int RuleId {get;} string[] ParticipatingLogicalFields {get;} }
IsValid is used t o check if t he rule is valid or not . The RuleId is used for m aking it possible t o
t ranslat e rules inform at ion and such. I t requires som e ext ra inform at ion, t ypically in t he form of long const ant list s, but I 'll leave t hat t o t he reader. The idea regarding ParticipatingLogicalFields is t hat t he consum er should be able t o m ark out t he fields t hat are affect ing a cert ain broken rule. And a rule class could look like t he following exam ple. This one is for checking t hat a dat e is wit hin a cert ain range: public class DateIsInRangeRule : RuleBase { public readonly DateTime MinDate; public readonly DateTime MaxDate; //Note: Only the "largest" constructor is shown here. public DateIsInRangeRule(DateTime minDate, DateTime maxDate, int ruleId, string[] participatingLogicalFields, string fieldName, object holder) : base(ruleId, participatingLogicalFields, fieldname , holder) { MinDate = minDate; MaxDate = maxDate; } public override bool IsValid { get { DateTime value = (DateTime)base.GetValue(); return (value >= MinDate && value = 1
That has t o be dealt wit h by a pre- read inst ead. A fift h is t he " reading way t oo m uch" problem when t oo m any com plex t ypes are read even t hough j ust a t iny am ount of inform at ion is really needed. This is especially com m on if you haven't used a som ewhat careful Aggregat e design and haven't used Lazy Load. Yet anot her classic problem is t hat of n+ 1 select s. Assum e you fet ch all Orders in t he dat abase and t hat OrderLines are Lazy Loaded. Then you t ouch each Order and it s OrderLines. Then t here will be a new SELECT issued for each Order's OrderLines. I f you don't use Lazy Load, t he problem will be sm aller, but t hen you run t he risk of get t ing int o t he problem j ust m ent ioned of fet ching t oo m uch in som e scenarios. Neit her of t hese exam ples is specific t o NHibernat e, but t hey are pret t y norm al for O/ R Mappers. An exam ple of a problem t hat I t hink is m ore relat ed t o NHibernat e is t hat of IDENTITY or sequences. Using such an I dent it y Field leads t o earlier INSERTs t o t he dat abase t han what you probably expect . All in all, it 's m y opinion t hat NHibernat e does a good j ob of fulfilling t hose overview requirem ent s, especially if you are happy wit h t he nat ure of how O/ R Mappers t ypically work and t he t radeoffs t hat are involved!
Classification OK, I have int roduced NHibernat e, and we have had a general look at how it deals wit h t he requirem ent s. We are now ready t o invest igat e NHibernat e furt her, and t his t im e wit h t he help of t he disposit ion used in Chapt er 8. But inst ead of discussing t he alt ernat ives in an abst ract way, I will now posit ion NHibernat e as being an exam ple of t he alt ernat ives t o each cat egory and also discuss how t he solut ion looks in a lit t le bit m ore det ail. First up is t he Dom ain Model st yle t hat NHibernat e expect s us t o use. We have already t ouched upon t his t opic, but let 's say a few m ore words about it .
Domain Model Style I said in Chapt er 8 t hat t he t hree m ost usual aspect s for Dom ain Model st yles t hat O/ R Mappers use include t he following: PI I nher it ance I nt erface im plem ent at ion Life isn't black and whit e, but in t his case I t hink t he choice is easy. The way I see it , NHibernat e gives us a high level of PI . You don't have t o inherit from cert ain base classes, and you don't have t o im plem ent cert ain int erfaces. You don't even have t o reference any NHibernat e assem blies in your Dom ain Model.
Mapper Style When it com es t o Mapper st yle, I said t hat Met adat a- based m appings are oft en exem plified in t wo different ways: Code generat ion Fram ework/ reflect ive Again, it 's pret t y easy t o posit ion NHibernat e because it 's a t ypical exam ple of a Fram ework/ reflect ive solut ion. Sure, t here is som e code generat ion support in NHibernat e, but t hat is t ypically used when generat ing one of t he t hree part s of t he Dom ain Model, Met adat a or dat abase schem a by looking at one ( or t wo) of t he ot her part s. For exam ple, assum e you have t he m et adat afrom t hat you can creat e dat abase schem a. That 's not what I m eant by code generat ion as t he m apper st yle. The m apper st yle is defined by how t he m apping is done at runt im e, and for t hat , NHibernat e doesn't use code generat ion. One except ion t o t he rule is when we look at what is done t o support Lazy Load [ Fowler PoEAA] . Then at runt im e NHibernat e will swap, for exam ple, your used list class for som e proxy inst ead. I n order for t his t o work, you have t o expose t he list as an int erface and not as a concret e class.
N ot e Not e t hat I didn't m ent ion t he usage of int erfaces as a m eans of lowering PI level earlier in t he chapt er. The reason for t his decision was t hat Lazy Loading is a feat ure and not som et hing you have t o use. The opinion on t hat varies, but as I see it , Lazy Loading is an opt im izat ion t echnique and not som et hing you have t o use all t he t im e.
I s it good or bad t hat NHibernat e is using t he Fram ework/ reflect ive approach? I really believe t hat it is m ost ly a good t hing when it com es t o O/ R Mappers.
Starting Point When you are working wit h a cert ain O/ R Mapper, you can oft en choose one or m ore st art ing point s from t he following list : Dat abase schem a Dom ain Model Mapping inform at ion NHibernat e allows you t o st art wit h any of t hese, and it 's not j ust a hack, but it 's support ed by design. Wit h t hat said, if you have t o st art from a legacy dat abase schem a t hat you aren't allowed t o change, having a look at som et hing like iBATI S [ iBATI S] m ight be a good idea.
API Focus The API focus com es in one of t wo alt ernat ives: Tables Dom ain Model But I said t hat t his doesn't really apply when it com es t o t he m ost com m on O/ R Mappers because t heir m ain purpose is t o provide a Dom ain Model view of t he world while using a Relat ional dat abase for t he persist ence. So t he answer here is easy. NHibernat e uses t he Dom ain Model as t he API focus.
Query Language Style Now it get s int erest ing. The querying capabilit ies cat egory is a cat egory where t he various solut ions differ quit e a lot . The subcat egories I defined in Chapt er 8 included t he following:
St ring- based, SQL- ish Query Obj ect - based NHibernat e has im plem ent at ions for bot h of t hese st yles, called HQL and Crit eria obj ect s in NHibernat e. I have already given an exam ple of what an HQL query could look like when searching for all Customers whose nam es st art wit h " Vol" . I f I want t o express t he sam e t hing wit h Crit eria obj ect s, it could look like t his: //A consumer IList result = _session.CreateCriteria(typeof(Customer)) .Add(Expression.Like("Name", "Vol%")) .List();
I n t he previous snippet , you also saw an exam ple of chaining of several m et hod calls. I t works because Add() ret urns an ICriteria j ust as CreateCriteria(). This helps t o m ake t he usage of crit eria queries slight ly m ore readable.
N ot e I n t his chapt er, I 'm j ust t ouching briefly on how different t hings are accom plished wit h NHibernat e, but in no ot her case is m y briefness so apparent as when it com es t o t he huge t opic of querying. Please refer t o [ Bauer/ King HiA] for m ore inform at ion.
I n Chapt er 8, I also m ent ioned t hat it 's good t o have t he possibilit y t o go over t o raw SQL as a last resort , t ypically t o solve perform ance problem s, and you can do t hat wit h NHibernat e. Bot h variant s are support ed so t hat you can get Ent it ies back and you can get t o t he raw connect ion and do what ever you want t o. NHibernat e also provides som et hing in bet ween t he ordinary query m echanism s and raw SQL t hat t hey call report queries ( also som et im es called flat t ening queries) . I t can look like t his in act ion: //A consumer string hql = "select new CustomerSnapshot(c.Id, c.Name) " + "from Customer c"; IList result = _session.CreateQuery(hql).List();
So here t he result won't have Customer inst ances, but CustomerSnapshot inst ances. For it t o work, you need t o provide a m at ching const ruct or on t he Value Obj ect ( CustomerSnapshot in t he previous case) and t o m ent ion CustomerSnapshot in a m apping file via an import direct ive like t his:
Not e t hat CustomerSnapshot j ust holds on t o flat dat a ( t ypically read- only) according t o NHibernat e. I dent it ies won't be t racked in t he I dent it y Map, and no changes will be t racked in t he Unit of Work. Talking about querying, t hat 's act ually exact ly what t he next cat egory is about as well.
Advanced Database Support The exam ples I provided in Chapt er 8 for " advanced dat abase support " ( not advanced for a dat abase guy, but perhaps for an obj ect guy) follow: Aggregat es Ordering Group by Scalar queries All four of t hese are support ed by bot h HQL and Crit eria obj ect s in NHibernat e. Let 's t ake an exam ple of each, beginning wit h an aggregat e query finding t he num ber of inst ances of Customer . I n HQL, it could look like t his: //A consumer string hql = "select count(*) from Customer"; int numberOfCustomers = (int)_session.CreateQuery(hql).UniqueResult();
N ot e Again, not e t hat it 's not t able nam es but classes t hat are used in HQL.
The second exam ple, t o order t he result set in t he dat abase so t hat it doesn't have t o be done in t he Dom ain Model ( which m ight oft en be a good t hing) , could look like t his in HQL. I n t his case we are ordering t he fet ch operat ion of all customers by Name. select from Customer order by Name
Group by is m ost ly used for report ing purposes, but you could st ill use it in your applicat ion. Here I 'm grouping on Name and count ing how m any inst ances for each nam e ( a bit t wist ed, but it shows t he synt ax) : //A consumer string hql = "select c.Name, count(*) from Customer c group by c.Name"; IList result = _session.CreateQuery(hql).List();
Finally, an exam ple of a scalar query could be t o only fet ch t he Name of t he Customers whose nam es st art wit h " Vol" ( which by now you'll recognize as m y favorit e crit erion) , but as a m at t er of fact you j ust saw anot her exam ple when I execut ed t he group by query earlier. To get t o t he values in t he result , t he IList called result cont ains a list of object arrays. Here's a snippet for cont inuing from t he previous exam ple, list ing t he nam es and t he num ber of inst ances for each nam e: //A consumer, continuing from previous snippet foreach (object[] o in result)
{ Console.WriteLine("{0}
{1}", (string)o[0], (int)o[1]);
}
Scalar queries are especially useful in sit uat ions where you find it t oo expensive t o fet ch com plet e Customer inst ances but want a m ore light weight result , and you don't want t o define a class. Of course, t he result won't be t ypesafe in t his case, but you could use a variat ion, nam ely t he one I t alked about earlier in t his chapt er called report query ( or a flat t ening query) . An alt ernat ive is t o provide m apping variat ions, but t hen I t hink it 's an even bigger t hing you have done. That is obviously violat ing t he Aggregat es because you are m aking it possible t o updat e t he inst ances wit hout m aking it m andat ory t o load t he com plet e Aggregat es. Warning bells should go off. Let 's end t his sect ion wit h som et hing t hat m ight be " obvious" t o you if you com e from an obj ect background, but m ight look like m agic if you com e from an SQL background. select o.Customer.Name, count(*) from Order o group by o.Customer.Name
The query it self was very sim ple, but not e t hat dat a is fet ched from t wo t ables, and t here is no m ent ion about t hat in t he from clause ( t hat is, t here is no j oin) . Of course, t he m apping inform at ion is used t o det erm ine what needs t o be done at execut ion t im e wit h t he HQL query.
Other Functionality Finally, we have a m ix of ot her funct ionalit y t o check for NHibernat e. W h a t ba ck e n ds a r e su ppor t e d? This is a t ypical exam ple of som et hing t hat could becom e a long and t edious feat ure enum erat ion, so I won't go int o det ail here. Let 's j ust say t hat NHibernat e support s several different dat abases ( m ost of t he m aj or com m ercial and open source dat abases) . Check t he sit e [ NHibernat e] for current inform at ion. You can also writ e your own plugin if you m iss t he dat abase you need t o work wit h. N ot j u st a n O/ R M a ppe r NHibernat e is pret t y focused on being an O/ R Mapper and not hing else. The philosophy is t o do one t hing and do it well.
N ot e Of course, NHibernat e does ot her t hings as well, but t hose ot her t hings are j ust not t he focus. And t hey are not t oo m any or t oo big eit her.
Adva n ce d ca ch in g
Advanced caching is one area where NHibernat e isn't really up t o par when com pared t o Hibernat e, but I t hink it 's rapidly becom ing bet t er. On t he ot her hand, t o m e t hat 's not usually a big deal. There are m any problem s wit h second- level caching, so t hat opt ion is oft en ruled out . Adva n ce d su ppor t for con t r ollin g t r a n sa ct ion s Support for m anual t ransact ion cont rol is pret t y good in NHibernat e. There are sit uat ions where you m ight be t aken by surprise if you don't wat ch out ( for inst ance, when you execut e a query in t he m iddle of a t ransact ion, NHibernat e will t hen Flush() changes t o t he dat abase before execut ing t he query) . That 's a t radeoff bet ween querying not going " t hrough" t he cache or ordinary t ransact ion cont rol. The good news is t hat it 's cont rollable by your code. To bind t his t o Chapt er 8, t he t ypical way of dealing wit h t ransact ions in NHibernat e is " m anual cont rol."
N ot e On t op of t hat , you can also build som et hing t hat uses any of t he m ore aut om at ic approaches m ent ioned in Chapt er 8.
Ope n sou r ce As I 've said already, NHibernat e is an open source proj ect , so you can invest igat e t he source code and even build your own branch. Ve r sion ( ge n e r a t ion ) I t 's hard t o decide what version NHibernat e is in. At t he t im e of writ ing, it 's called version 1 by t he t eam building it , but at t he sam e t im e, I would say t hat t he qualit y is higher t han wit h t ypical first versions. NHibernat e is also a port from Hibernat e, which is second generat ion or act ually t hird now, but t hat 's not reflect ed in NHibernat e. Anyway, let 's call it generat ion 1. Let 's have a look at how NHibernat e posit ions it self in t he classificat ions from a t ot ally different angle: t he infrast ruct ure pat t erns angle.
Another Classification: Infrastructure Patterns Wit hout furt her ado, let 's get st art ed wit h t ype of m et adat a.
Metadata Mapping: Type of Metadata I n Chapt er 8 , I defined t hree different t ypes of m et adat a for t he Met adat a Mapping pat t ern [ Fowler PoEAA] : XML docum ent ( s) or ot her docum ent form at s At t ribut es Source code This cat egory was sim ple regarding NHibernat e because it s m et adat a is t ypically XML docum ent s. You can describe t he m et adat a in source code as well in t he works, but t hat 's not t he m ainst ream t hing t o do at all. There are at t ribut e- based approaches as well, but t hey generat e m et adat a as XML docum ent s.
Identity Field I have already t ouched on t he m at t er of how you deal wit h I dent it y Fields in NHibernat e, but I 'd like t o go a lit t le deeper. Earlier I showed how t o m ap an I dent it y Field t hat is of t he Guid t ype. Let 's t ake a look at t he Product Ent it y, which has an int for t he I dent it y Field, and t he value is generat ed wit h an IDENTITY ( aut o increm ent ing colum n) in t he dat abase. I t could t hen look like t his:
As I said in Chapt er 8 , when it com es t o new ent it ies, t he values for I dent it y Fields can be generat ed in at least four different layers: Consum er Dom ain Model Dat abase O/ R Mapper I n t he case of t he guid, you can generat e it in all t he layers, but it 's recom m ended t hat you do it in t he O/ R Mapper. I n t he exam ple wit h IDENTITY j ust shown, t he value is generat ed in t he dat abase, and t hat act ually affect s t he program m ing m odel a lot . First , t here will be a dat abase j um p earlier
t han you expect . Second, t here m ight be an INSERT j ust t o grab t he IDENTITY value. Bot h exam ples are som et hing I consider t o be problem s, and t herefore I don't like t o use IDENTITY s m ore t han is necessary.
NHibernate Supports COMBs As a m at t er of fact , Guid s are oft en a very nice solut ion. But perhaps som e of you have read m y art icle about a possible cost of INSERT s when you use Guid s as prim ary keys [ Nilsson COMB] . The problem is t hat t he INSERT t hroughput m ight be m any t im es lower for t ables wit h Guid s for prim ary keys t han if you use int s. That 's not a problem you will t ypically get , but it m ight com e and bit e you if you have very large t ables and a high INSERT load. I suggest ed a solut ion t o t he problem in t he art icle I called " COMB." I t 's basically a Guid , but kind of a sequent ial one. I was very surprised t o see t hat NHibernat e added support for COMB in a specific alpha version. To use t he COMB generat or, t he m et adat a could look like t his ( t he only difference is t hat generat or is guid.comb and not j ust guid ) :
I t is easiest for you if you let NHibernat e generat e t he Guids for you, but t hat 's not always possible, of course. Som et im es you m ust use assigned I dent it y Fields inst ead, which m akes it quit e a bit harder. For exam ple, you can't let NHibernat e det erm ine using UPDATE or INSERT by looking at unsaved-value . I nst ead, you will t ypically t ell NHibernat e what t o do wit h explicit calls t o Save() or Update() inst ead of SaveOrUpdate() .
N ot e As I said earlier in t his chapt er, you can use t he version t ag +unsaved-value t ag for helping you out when you prefer t o set t he I dent it y Field values on your own.
These were j ust a few exam ples of st rat egies for I dent it y Fields. NHibernat e support s m any m ore. You can also plug in your own st rat egies quit e easily.
Foreign Key Mapping A foreign key m apping could look like t his in a m apping file; here ( in t he Order.hbm .xm l file) it is describing t hat an Order has a Customer :
Wit hout going int o det ails, I would say t hat NHibernat e has st rong support for m any relat ionship st yles t hat will solve m ost of your needs in t hat area. I t 's not very easy, t hough, t o learn t o use all variat ions effect ively, but you can go a long way wit h a few basic ones.
On t he ot her hand, what is pret t y easy is using t he Em bedded Value pat t ern [ Fowler PoEAA] wit h NHibernat e.
Embedded Value NHibernat e uses t he word component ( as if t hat word didn't have enough different m eanings) for describing an Em bedded Value. Let 's repeat again what it could look like in t he m apping file t o describe t hat a Customer has an Address :
Not e t hat t he Customers t able cont ains all colum ns, but t he Customer class has j ust an Address field. When t hat is in place, using t he Em bedded Value from t he consum er is very easy t o underst and. I t could look like t his, for exam ple: Console.WriteLine(aCustomer.Address.Street);
Som et hing t o t hink about , however, is whet her t he Value Obj ect Address should be im m ut able or not . I would probably choose im m ut able in t his part icular exam ple, but it 's not always an easy choice. I f you decide t o use m ut able, you could writ e t he code like t his inst ead of inst ant iat ing a new Address : aCustomer.Address.Street = "Large Street 42";
N ot e I f you have an Em bedded Value t hat should be used in m any ot her classes, it could be a good idea t o im plem ent IUserType so t hat you don't have t o describe t he m apping over and over again. That 's also t he case if t he st orage is different from t he t ype t o expose. The t ranslat ion is t hen done by t he IUserType im plem ent at ion. Consequent ly, one downside is t hat t he Dom ain Model will have t o refer t o nhibernat e.dll, or you will have t o have t he specific code in a separat e assem bly and inherit t he basic Dom ain Model class inst ead.
I t ry t o avoid t his if possible. When t he ordinary approach for Em bedded Value can be used, everyt hing is j ust fine. For t ricky cases, it 's oft en possible t o do t he t ranslat ion m anually inst ead in propert y get / set from / t o privat e fields and t hen m ap t hose privat e fields t o t he dat abase. A t ypical exam ple of when I avoid a cert ain const ruct or deal wit h it wit h cust om get / set code is when it com es t o applying t he t ypesafe enum pat t ern [ Bloch Effect ive Java] ( for exam ple, for adding behavior t o enum s) . First , I t hink t wice if I can't j ust live wit h an ordinary C# enum . Second choice is cust om get / set code so I don't have t o deal wit h IUserType . I t feels like a quit e big solut ion for a t iny t hing. To end t his not e, j ust one m ore t hing: Don't exaggerat e your t ries t o avoid a reference t o nhibernat e.dll in your Dom ain Model. I f t he benefit is bigger t han t he cost ...
Inheritance Solutions NHibernat e support s all t hree different inherit ance solut ions, nam ely Single Table I nherit ance, Class Table I nherit ance, and Concret e Table I nherit ance [ Fowler PoEAA] . I f we assum e t hat Customer and Vendor inherit s from Company , t he m apping inform at ion could look like t his:
Then, som et hing like t he following is needed for each subclass, where t he specifics are described ( t hat is, t he m apping inform at ion t hat isn't for t he Company class) :
From t hat you underst and t hat t he only variat ion in each case is a single specific field, CustomerNumber and VendorNumber . I n t his case, we will have j ust a single Companies t able, and it would look like t his ( m eaning t hat we used Single Table I nherit ance here) : create table Companies ( Id UNIQUEIDENTIFIER not null, Type VARCHAR(8) not null, Version INT not null, Name VARCHAR(100) not null, Street VARCHAR(50) not null, PostalCode VARCHAR(10) not null, Town VARCHAR(50) not null, Country VARCHAR(50) not null, CustomerNumber INT null, VendorNumber INT null, primary key (Id)
)
That m eans t hat for Vendors , CustomerNumber will be NULL , and vice versa. The Type colum n will have t he value " Customer " or " Vendor " ( or norm ally som e sm aller sym bols) .
N ot e I know, som e of you are feeling so uncom fort able regarding t his exam ple because you prefer t o use som et hing like t he Part y archet ype inst ead [ Arlow/ Neust adt Archet ype Pat t erns] . Or at least you prefer not such an overuse of inherit ance, if t he only variat ion was a single field and not hing else. My int ent ion was j ust t o show an obvious and clear exam ple.
And as you would probably expect , when t he inherit ance m apping is in place, t he consum er code can forget about how t he inherit ance hierarchy is act ually st ored and focus on how t o use t he inherit ance hierarchy inst ead.
Identity Map NHibernat e uses an I dent it y Map on an ISession level. You can easily see t his if you read an inst ance by Id wit h Load() , m ake a change t o t he row direct ly in t he dat abase, and t hen read t he inst ance again wit h Load() ( wit h t he sam e ISession inst ance) . You won't see t he change because NHibernat e didn't roundt rip t o t he dat abase aft er t he first Load() , but inst ead t he inst ance was grabbed from t he I dent it y Map. NHibernat e also uses t he I dent it y Map when it com es t o querying; not for finding inst ances, but for placing t he I dent it ies in t he Map t o support fut ure Load() calls from t he I dent it y Map. I f you'd like t o force an inst ance away from t he I dent it y Map, you can call Evict() on t he ISession , or Clear() t o clear t he I dent it y Map from all inst ances, or of course Close() t he ISession .
Unit of Work I t probably won't com e as a surprise t o learn t hat NHibernat e uses t he Unit of Work pat t ern [ Fowler PoEAA] as well, and again it 's dealt wit h in t he ISession . That said, t he im plem ent at ion is pret t y different from t he m ost t ypical one. I nst ead of regist ering what has happened wit h t he Unit of Work, NHibernat e t akes a snapshot of t he inst ances when t hey are read from t he dat abase. At flush t im e, t he inst ances known by ISession are com pared t o t he snapshot s t o creat e a Unit of Work at t hat point in t im e. To be clear, in a way delet es are regist ered and so are not ificat ions of new inst ances wit h, for exam ple, SaveOrUpdate() . As always, t here are pros and cons t o t his solut ion, and one obvious drawback is when t here are m any inst ances t o invest igat e, only t o find t hat j ust one should be flushed. That 's one reason for why Hibernat e ( t hat 's right , Hibernat e, not N Hibernat e, at least not at t he t im e of t his writ ing) 3 has been changed regarding t his.
Lazy Load/Eager Load
I f you do want t o use ( aut om at ic) Lazy Load [ Fowler PoEAA] wit h NHibernat e, t hat 's easily done. You j ust declare it in t he m et adat a like t his, for exam ple:
b> c> "); }
I t doesn't even com pile. I need t o add Fill() and Render() t o PickCandyMdl. That 's easy; I j ust add t wo em pt y public m et hods, one nam ed Fill( ) and one nam ed Render( ) . But what is t he t est expressing? What am I assert ing? The assert ion is expressing what t he " form " should look like nowin a very abst ract way. The st ring SrcPanel is used inst ead of t he HTML t able ( t he left one in t he form ) t hat we're aim ing for at t he end. Where does SrcPanel com e from ? Well, SrcPanel is a st ring in t he t est fixt ure ( t he class) t hat is m anipulat ed by m y self- shunt ed [ Feat hers Self- Shunt ] im plem ent at ion of t he PickCandyView . This is t he recurring pat t ern when I m ock t he view. [TestFixture] public class PickCandyTests: PickCandyView { void PickCandyView.SourceItem(string text, bool pickable) { SrcPanel += text + (pickable ? ">" : string.Empty) + " "; } PickCandyMdl mdl; string SrcPanel; [SetUp] protected void Setup()
{ mdl = new PickCandyMdl(); SrcPanel = string.Empty; } ...snip...
I t com piles, but it t urns red. What 's m issing? Well, t he form m odel, mdl of t ype PickCandyMdl, needs t o know about it s view. That 's easy! I add a SetView( ) m et hod t o t he m odel. ( By t he way, t his is one of t he few occasions where I use " int erface im plem ent at ion" t he way I did wit h PickCandyView.SourceItem( ) , as in { int erface nam e} .{ m em ber nam e} .)
Private or Public Implementation of Interface Methods? I m plem ent at ion of m et hods can only exist on classes. I f a class, Foo , im plem ent s an int er face, Bar , t he int erface's m et hods are a part of t hat class. I would in m ost cases im plem ent Bar 's m em bers as public on Foo . Rem em ber t hat t he int erface of t he class it self is all it s public m em bers. Foo is a Bar , hence t he m em bers of Bar are a part of Foo . I t j ust so happens t hat I im plem ent an int erface's m em bers as privat e in ot her sit uat ions as a m eans t o solve nam e collisions, as in t he case of a self- shunt ing m ock. Here I don't act ually see t he int erfaces ( PickCandyView and CandySourceRetriever) as a part of t he t est fixt ure's int erface.
The form m odel needs t o know how t o obt ain all Candy Sources. This could be easily fixed by j ust supplying t he m odel wit h a list of Candy Sources. But inst ead I 'm going t o show you anot her lit t le t rick t hat im proves t est abilit y in t he general case. I 'm let t ing t he form m odel ( or rat her t hat package/ assem bly) own an int erface represent ing t he needed ret rieve operat ion. public interface CandySourceRetriever { IList Retrieve(); }
Why Not the I-name Convention for Interfaces? Let m e ask youwhat good would it do you? This convent ion cam e from an environm ent where t he not ion of an int erface it self was a convent ion, a rule, and not a first - class language concept ( t hat changed, m ore or less, wit h m idl) . When it com es t o t rying t o inst ant iat e an int erface ( wit h new ) t he C# com piler ( at least m y version) t reat s abst ract classes and int erfaces as one and t he sam e. [View full width]public interface Bar {} public class Foo { public static void Main() {new Bar();} } ingo.cs(2,47): error CS0144: Cannot create an instance of the abstract class or interface 'Bar'
Why do we not have an A- nam e convent ion for abst ract classes? Because when you refer t o a t ype t hat has m et hods and propert ies, you don't care if it is an int erface, abst ract class, or concret e dit t o. The I - nam e convent ion seem s t o have been adopt ed by t he Fram ework Class Library ( FCL) wit hout furt her considerat ion.
Alternative Explanation/Rambling The I - nam e convent ion m ade sense in COM. COM wasn't really obj ect - orient ed, was it ? I t was ( well, is) com ponent - orient ed. I s t here a reason t o t reat int erfaces of com ponent s as som et hing inherent ly different from t he com ponent it self? Maybe, but t he quest ion t o ask is if int erfaces in FCL are int erfaces t o com ponent s or if t hey are t he answer/ com prom ise t o t he difficult ( com piler) problem of m ult iple inherit ance. I f so, an int erface in CLR is very close t o an abst ract class. There's no A- nam e convent ion for abst ract classes. Drop t he I - nam e convent ion; it serves no purpose. When you refer/ use a t ype t hat has m et hods and propert ies, you don't care if it is an int erface, abst ract class, or concret e dit t o.
I 'll also m ock t his int erface as a self- shunt . Of course, t he form m odel needs t o know about it s ret riever. Wit h all t his in account , t he t est fixt ure st art s as t he following: [TestFixture] public class PickCandyTests: PickCandyView, CandySourceRetriever { void PickCandyView.SourceItem(string text, bool pickable) { SrcPanel += text + (pickable ? ">" : string.Empty) + " "; } IList CandySourceRetriever.Retrieve() { return sources; } PickCandyMdl mdl; ArrayList sources;
string SrcPanel; [SetUp] protected void Setup() { mdl = new PickCandyMdl(); mdl.SetView(this); mdl.SetRetriever(this); sources = new ArrayList(); sources.Add(new CandySource("a")); // think chocolate sources.Add(new CandySource("b")); // ... lollipop sources.Add(new CandySource("c")); SrcPanel = string.Empty; } ...snip...
Of course, t he t est st ill com es out red because we haven't im plem ent ed anyt hing in PickCandyMdl yet . Let 's do it now. [Serializable] public class PickCandyMdl { public void Fill() { _retrieved = _retriever.Retrieve(); } public void Render() { foreach(CandySource s in _retrieved) _view.SourceItem(s.CandyType, true); } public void SetView(PickCandyView v) { _view = v; } public void SetRetriever(CandySourceRetriever r) { _retriever = r; } [NonSerialized] PickCandyView _view; CandySourceRetriever _retriever; IList _retrieved; }
As you can see, m ost of t his is t rivial. I n Fill() we pick up t he available candy sources and in Render( ) we it erat e over t hem passing inform at ion t o t he view for it t o display. Not e t hat we've faked t he im plem ent at ion so far; t he candy sources are not going t o be " pickable" ( t he t rue const ant ) in all circum st ances. SetView( ) and SetRetriever( ) are t rivial. The [ NonSerialized ] at t ribut e on _ view is needed when you use out of process handling of t he Session st at e in t he ASP.NET applicat ion. When we run t he t est now, it t urns green. Hooray!
Picking Candy We need t o be able t o pick candy from t he source list . Let 's express t hat as a t est . [Test] public void PickingCandy () { mdl.Fill(); mdl.PickAt(0); mdl.Render(); A.AreEqual(SrcPanel, "a> b> c> "); A.AreEqual(StashPanel, "a "); }
StashPanel is of course t he t est fixt ure represent at ion of t he right - hand panel displaying what you ( I
m ean your kids) have picked so far ( a HTML t able in t he real form ) . A new m et hod is needed, PickAt() .We also need a new m et hod on t he view. public interface PickCandyView { void SourceItem(string text, bool pickable); void StashItem(string text); }
PickAt( ) is an excellent incit em ent t o int roduce CandyStash t o t he form m odel, only hint ed at as _stash in t he following code:
public void PickAt(int index) { Candy c = ((CandySource)_retrieved[index]).GrabOne(); _stash.Add(c); }
We also need t o com plem ent Render( ) . public void Render() { foreach(CandySource s in _retrieved) _view.SourceItem(s.CandyType, true); foreach(Candy c in _stash) _view.StashItem(c.CandyType); }
I 'm sure you can im plem ent t he StashItem( ) m et hod t he sam e way I im plem ent ed SourceItem( ) . Running t he t est now t akes us t o green again. Before I m ove on, I com plem ent m y first t est t o assert t hat t he StashPanel is em pt y ( string.Empty) .
Handling the Display Dynamics When we picked t wo of t he sam e candy t ype, t hat t ype is not supposed t o be " pickable" anym ore
( have a look at t he first figure of t he applicat ion again) . Let 's express t hat as a t est . [Test] public void PickingTwoOfSameKind() { mdl.Fill(); mdl.PickAt(0); mdl.PickAt(0); mdl.Render(); A.AreEqual(SrcPanel, "a b> c> "); A.AreEqual(StashPanel, "a a "); }
The st ash of candy now cont ains t wo " a" s. The m issing great er t han sym bol ( > ) in SrcPanel aft er t he " a" is t he key t hing here. This t est doesn't int roduce any new m et hods, but it com es out red due t o t he short cut im plem ent at ion of Render( ) we m ade earlier. Rem em ber t he t rue const ant ? I sn't t he OkToAdd( ) m et hod on CandyStash exact ly what we're looking for here? public void Render() { foreach(CandySource s in _retrieved) _view.SourceItem(s.CandyType, _stash.OkToAdd(s.GrabOne())); foreach(Candy c in _stash) _view.StashItem(c.CandyType); }
I grab one from t he source ( s.GrabOne( ) ) and pass it t o OkToAdd( ) t o see if it is " pickable." The CandySource is an infinit e source ( it is act ually an obj ect fact ory) . I n real life you'd probably rat her die t han wast e chocolat e bars like t his, but t his isn't real life, t his is soft ware. I f we picked t hree of an allowed m ix, we're not allowed t o pick anyt hing else, right ? Let 's express t hat as a t est , t oo. [Test] public void PickingThreeMix() { mdl.Fill(); mdl.PickAt(0); mdl.PickAt(0); mdl.PickAt(1); mdl.Render(); A.AreEqual(SrcPanel, "a b c "); A.AreEqual(StashPanel, "a a b "); }
This t urns green right away. The OkToAdd() check for " pickable" was a st roke of genius, don't you t hink? We shouldn't be surprised, because it ( OkToAdd()) capt ures t he very essence of our business rule.
The Web Form Implementation There are several ways t o handle st at e in a Web applicat ion. I n t his scenario, I 've disabled view st at e on t he docum ent form and am relying solely on t he Session obj ect on t he server. I build t he page upon hit t ing t he Web form , and I rebuild it j ust before ending t he processing ( in t he PreRender event ) t o reflect t he m odel's current st at e. Rem em ber t hat t he m odel's st at e changes when we pick candy for our st ash.
For t his I 've added a call t o MyInit( ) first in t he st andard OnInit( ) m et hod of t he Web form creat ed by Visual St udio .Net . void MyInit() { if (!IsPostBack) { _mdl = new PickCandyMdl(); Session["PickCandyMdl"] = _mdl; _mdl.SetRetriever(CreateRetriever()); _mdl.Fill(); } else { _mdl = (PickCandyMdl)Session["PickCandyMdl"]; } _mdl.SetView(this); MdlRender(); } PickCandyMdl _mdl; HtmlTable _srcTbl, _stashTbl; void MdlRender() { _srcTbl = new HtmlTable(); srcpnl.Controls.Add(_srcTbl); _srcTbl.Width = "100%"; _stashTbl = new HtmlTable(); stashpnl.Controls.Add(_stashTbl); _stashTbl.Width = "100%"; _mdl.Render(); } private void WebForm1_PreRender(object sender, System.EventArgs e) { srcpnl.Controls.Clear(); stashpnl.Controls.Clear(); MdlRender(); }
The srcpnl, t he candy source panel, and stashpnl , t he candy st ash panel, are Panels ( t he cont rol in t he WebControls nam espace) I 've added wit h t he form designer in Visual St udio. Of course, t he form im plem ent s PickCandyView . The code is fairly sim ple. Any decisions m ade ( if st at em ent s) concern appearance only, not t he st at e of t he int eract ion. This is crucial. The m odel m ust be responsible for t he lat t er. void PickCandyView.SourceItem(string text, bool pickable) { int index = _srcTbl.Rows.Count; HtmlTableRow tr = AddTR(_srcTbl); HtmlTableCell td = AddTD(tr);
td.InnerText = text; td = AddTD(tr); if (pickable) { LinkButton lb = new LinkButton(); td.Controls.Add(lb); lb.ID = "pick@" + index.ToString(); lb.Text = ">>"; lb.Click += new EventHandler(Pick_Click); } } void PickCandyView.StashItem(string text) { AddTD(AddTR(_stashTbl)).InnerText = text; }
And finally, here's t he pick callback m et hod: private void Pick_Click(object sender, EventArgs e) { _mdl.PickAt(IndexFromID(sender)); }
Yes, I t rust you t o read bet ween t he lines. The only possibly non- t rivial t hing I left for you t o figure out is t he CreateRetriever( ) m et hod. You've seen som et hing sim ilar t o what 's needed in t he t est fixt ure. Figure 11- 5 shows t he app in sort of a com ic st rip. The kid picks t wo chocolat e bars followed by a bag of peanut s.
Figu r e 1 1 - 5 . Th e ca n dy- pick in g a pplica t ion
Summary Most code for handling t he GUI is in a m odel of t he int eract ion. Only a sm all port ion of t rivial code is left unt est ed. Graphical appearance is, however, not what 's t est ed wit h t his m et hodology but rat her t he dynam ics of t he GUI . I n t his quit e unfinished exam ple I got away wit h very lit t le handling in t he form m odel. Wrapping t he candy picked in, say, a CandyItem obj ect is one possible developm ent if we decided t o add feat ures for regret t ing a choice ( ret urn t o t he source) .
Mocking with NMock Let m e, as a sm all addendum and appet izer, show you anot her way t o m ock during t est ing using NMock [ NMock] . I 'll be very brief, let t ing t he t est code speak for it self allowing you t o com pare it wit h t he previous t est s. Let 's first have a look at t he set up. Let m e j ust point out t hat t he t est fixt ure class doesn't im plem ent t he int erfaces PickCandyView and CandySourceRetriever as it did earlier in our self- shunt ing fixt ure. PickCandyMdl mdl; IMock data, view; const bool Pickable=true, NotPickable=false; [SetUp] protected void Setup() { data = new DynamicMock(typeof(CandySourceRetriever)); view = new DynamicMock(typeof(PickCandyView)); mdl = new PickCandyMdl(); mdl.SetView((PickCandyView)view.MockInstance); mdl.SetRetriever((CandySourceRetriever)data.MockInstance); ArrayList sources = new ArrayList(); sources.Add(new CandySource("a")); // think chocolate sources.Add(new CandySource("b")); // ... lollipop sources.Add(new CandySource("c")); data.SetupResult("Retrieve", sources); }
The inst ant iat ion of DynamicMock( s) does t he m agic and creat es obj ect s adhering t o t he int erfaces specified. The dynam ically " coded and com piled" obj ect of a m ock is found as IMock.MockInstance . Wit h SetupResult( ) , we t ell t he dat a m ock t o ret urn sources when it get s called wit h Retrieve ( ) . Now let 's have a look at t he equivalence of t he first t est from earlier. [Test] public void FillSourcePanel() { // Expectations view.Expect("SourceItem", "a", Pickable); view.Expect("SourceItem", "b", Pickable); view.Expect("SourceItem", "c", Pickable); view.ExpectNoCall("StashItem", typeof(string)); // Execution mdl.Fill(); mdl.Render(); // Verification view.Verify(); }
The com m ent s in t he code are t here j ust t o show you t he general phases, besides set up, when t est ing wit h NMock. We declare t hat we're expect ing SourceItem () of PickCandyView t o be called t hree t im es and what values we expect . We also st at e t hat StashItem () is not t o be called. I n t he execut ion phase we, well, do t he execut ion of t he obj ect we're t est ing by calling som e m et hods on it . And finally, we ask t he view m ock t o verify t hat our expect at ions were m et during execut ion.
I t hink it is best t o follow t he red- green ( - refact or) form ula of TDD. I n t he previous t est at least , if you forget t o call Verify() you'll end up wit h a green t est regardless of whet her t he expect at ions are m et or not . I f you first require a red run, you're sure t hat you're act ually verifying ( assert ing) som et hing. Thanks, I ngo! Again, t est ing is an ext rem ely im port ant fact or and som et hing wort h preparing and adj ust ing for. Now we have discussed t wo different variat ions regarding t he UI , but wit h sim ilarit ies on how t hat can be done. Last but not least , we will discuss som e aspect s regarding t he dat a for t he UI in cont rast t o t he dat a in t he Dom ain Model. Som e m ight feel m y approach is " I have a ham m er; let 's find nails" when t alking about m apping for t he present at ion services j ust because m apping is a popular solut ion for t he persist ence services. But I t hink it 's very m uch in t he spirit of DDD t o focus on t he core, t he Dom ain Model, as m uch as possible and t hen creat e sim ple, flexible solut ions for dealing wit h t he infrast ruct ural problem s " around" t he Dom ain Model, such as t ransform ing t he Dom ain Model int o anot her m odel for anot her t ier. That 's why I t hink obj ect - t o- obj ect m apping and UI m apping have a lot of pot ent ial, especially for rout ine t asks t hat are j ust a wast e of t im e t o hand code. I n t his sect ion, Mat s Helander discusses som e aspect s of UI Mapping, focusing on aut om at ic m apping bet ween a present at ion friendly m odel ( such as a Present at ion Model [ Fowler PoEAA2] ) and t he Dom ain Model. Over t o Mat s.
Mapping and Wrapping By Mat s Helander Wit h t he aid of O/ R Mapping, we are able t o connect our Dom ain Model t o t he relat ional dat abase. Toget her wit h t he business m et hods t hat we add t o t he Dom ain Model classes and possibly t o a Service Layer [ Fowler PoEAA] , we have a pret t y solid archit ect ure in place for t he st ruct ural and funct ional foundat ion of t he applicat ion. That brings us t o t he last st ret cht he Present at ion Layer ( PL) . While you and I m ay consider t he Dom ain Model t o be t he heart of t he applicat ion, end users are likely t o t hink of what ever t hey are present ed wit h as t he final basis for j udgm ent . This m eans t hat no m at t er how cool your Dom ain Model ( and support ing Service Layer) is, no one will applaud your work ( except a fellow developer) unless you provide access t o your m odel via som e whiz- bang UI . The good news is t hat , if you're lucky, connect ing a PL t o your Dom ain Model can be a piece of cake. The bad news is t hat , whenever you're not t hat lucky, it can be quit e a challenge. I n t he sim plest case, your Dom ain Model is already " present able" and you could j ust dat a bind direct ly t o t he Dom ain Model obj ect s. However, t he int erface of an applicat ion frequent ly dem ands t hat inform at ion is present ed in a m ore " user friendly" form t han it was in t he Dom ain Model.
Mapping and Wrapping Whenever t he Dom ain Model obj ect s fall short of being im m ediat ely present able, you'll have t o m ake a choice: Should you j ust wrap your DM obj ect s or should you m ap a new set of obj ect s t o t hem ? For exam ple, say t hat you have a DM class called Person, wit h FirstName and LastName propert ies. However, in t he UI , you want t o have a single t ext box for edit ing a person's full nam e. One way of doing t his, of course, is t o have som e code in your UI t hat sim ply reads from t he DM Employee obj ect 's first and last nam es and writ es t o t he t ext box and t hat reads from t he t ext box and writ es t o t he Employee obj ect propert ies when t he user hit s Save. A lot of t he t im e, in t he so called " real world," t his is how UI requirem ent s are solvedby what is essent ially an accum ulat ing heap of hacks. I n t he end, t his is not t he pat h t oward a m aint ainable applicat ion, and t hat is what m ot ivat es us t o look for a design t hat will scale wit h increasing com plexit y and size of t he applicat ion. One recurring solut ion is t o com plem ent t he Dom ain Model obj ect s wit h a new set of obj ect s, oft en referred t o as t he Present at ion Model ( PM) . The Present at ion Model obj ect s are a part of t he PL and should have a st ruct ure and behavior m at ching t he requirem ent s of t he PL ( in effect , m at ching t he needs of t he UI ) . I n our exam ple, t he PM Person class would have a FullName propert y rat her t han FirstName and LastName propert ies found in t he Dom ain Model Person class.
Why not j ust put t he GetFullName () m et hod in t he Dom ain Model Person class? We could, if it had a use in som e business logic operat ion, but if t he only use of t he GetFullName () m et hod is in t he PL, t he m et hod should go in t he PM and not in t he Dom ain Model. Keeping your Dom ain Model free of non- business aspect s, such as present at ion and persist ence, is every bit as im port ant as it was t o keep t he Business Logic Layer of yest eryear free of Present at ion logic and Dat a Access logic, and for j ust t he sam e reason: Keeping t he Dom ain Model free from any non- business aspect s is key t o keeping t he " heart " of your applicat ion underst andable and m aint ainable. So for t his exam ple, assum e t hat GetFullName() is only used in present at ion and is t hus a good candidat e for a m et hod t hat should go on a PM rat her t han on t he Dom ain Model. The quest ion becom es how we can connect t he PM classes t o t he Dom ain Model classes, and t hus we arrive at t he choice: Mapping or Wr apping?
Wrapping the Domain Model with the Presentation Model Wrapping is oft en t he easier solut ion, requiring no addit ional fram ework for st at e m anagem ent in t he PM. The idea here is t hat you pass t he DM Employee obj ect t o t he const ruct or m et hod of t he PM Employee obj ect . The PM Employee t hen keeps an int ernal reference t o t he DM obj ect and delegat es all calls t o t he DM propert ies. See t he following code list ing. //Presentation Model wrapper object namespace MyCompany.MyApplication.Presentation { public class EmployeeWrapper { private Employee employee; public EmployeeWrapper (Employee employee) { this.employee = employee; } public int Id { get { return this.employee.Id; } set { this.employee.Id == value; } } public string FullName { get { return this.employee.FirstName + " " + this.employee.LastName; } set { //This should of course be complemented with //some more cunning logic, as well as some //verification, but this is just an example... string[] names = value.Split(" ".ToCharArray()
, 2); this.employee.FirstName = names[0]; this.employee.LastName = names[1]; } } } }
Using t he EmployeeWrapper class is a m at t er of bringing up an Employee obj ect from t he Dom ain Model and t hen passing it t o t he EmployeeWrapper const ruct or ( see t he following code list ing) . //Using the Presentation Model wrapper object Employee employee = employeeRepository.GetEmployeeById(42); EmployeeWrapper employeeWrapper = new EmployeeWrapper(employee); SomeControl.DataSource = employeeWrapper;
Big advant ages wit h t he wrapping approach include sim plicit y and flexibilit y. You can writ e pret t y m uch any t ransform at ions you like in your wrapper obj ect s, t ransform at ions far m ore com plex t han our relat ively sim ple FullName t ransform at ion, if required. A big drawback is t hat you will find yourself writ ing a lot of repet it ive code in your wrapper classes for delegat ing t o propert ies t hat don't need any t ransform at ionlike t he Id propert y in t he previous exam ple. This m ay m ake it t em pt ing t o j ust go ahead and let t he Present at ion Model obj ect s inherit from t he Dom ain Model obj ect s inst ead, overriding what ever propert ies t hat need t ransform at ion, but leaving all ot hers as t hey are. The problem wit h t his is t hat t hen you are st uck wit h t he public API of t he Dom ain Model, because all public m em bers of a superclass are inherit ed by t he subclass. I n our exam ple, we could have let t he PM Employee inherit from t he Dom ain Model Employee , and we could have added t he FullName propert y t o t he PM Employee , but t he PM Employee would also have exposed t he FirstName and LastName propert ies from t he Dom ain Model Employee , because t hese public propert ies would be inherit ed. Wrapping your Dom ain Model obj ect s rat her t han inherit ing from t hem gives a higher level of encapsulat ion t hat is usually well wort h t he ext ra, adm it t edly t edious, work of delegat ing bet ween PM and Dom ain Model propert ies. Furt herm ore, t he delegat ion code for propert ies t hat don't require any advanced t ransform at ions can be fruit fully at t acked wit h code generat ion solut ions or even j ust code snippet t em plat es. I f all else fails, let t he int ern writ e t he boilerplat e code!
Mapping the Presentation Model to Domain Model The alt ernat ive t o wrapping your DM obj ect s wit h your PM obj ect s is t o act ually copy t he dat a back and fort h bet ween t he DM obj ect s and t he PM obj ect s. I n t his case, you'd be writ ing code like t he following: //Moving data manually between Presentation Model //and Domain Model Employee employee = employeeRepository.GetEmployeeById(42);
EmployeeView employeeView = new EmployeeView(); employeeView.Id = employee.Id; employeeView.Salary = employee.Salary; employeeView.FullName = employee.FirstName + " " + employee.LastName;
As wit h t he wrapping exam ple, t he code for copying t he dat a could be placed in t he const ruct or of t he PM obj ect , in which case t he const ruct or would accept t he DM obj ect in a param et er. The previous exam ple shows only t he operat ive lines of code for clarit y. I f you suffer from t he sam e afflict ion as I , abst ract us m anicus, you will im m ediat ely recognize t he pot ent ial for abst ract ing t his work int o a fram ework of som e kind. I n short , we should be able t o specify ( in, say, an XML file) what PM propert ies m ap t o what DM propert ies and t hen let t he fram ework use reflect ion t o m ove t he dat a. Such a fram ework would have t o m ap one set of obj ect s t o anot her set of obj ect s, and so t he logical t erm for t his t ype of fram ework would be " Obj ect / Obj ect Mapper" or O/ O Mapper for short ! I f you decide t o writ e such a fram ework, you m ay realize t hat solving t he non- t ransform at ional cases, such as m apping Id t o Id and Salary t o Salary in t he previous case, should be a pret t y st raight forward t ask. Mapping FullName t o First-Name and LastName , on t he ot her hand, is t rickier. I t is quit e solvable, but t he quest ion becom es if it is t he right approach t o t ry t o build advanced t ransform at ion services int o t he O/ O Fram ework. The alt ernat ive is t o avoid charging t he O/ O Mapper wit h responsibilit y for act ual st ruct ural t ransform at ions and let it concent rat e on j ust m oving dat a back and fort h bet ween obj ect s t hat share t he sam e or a very sim ilar st ruct ure. I n our exam ple, t he solut ion becom es giving t he PM Employee class a FirstName and a LastName propert y in addit ion t o it s FullName propert y, but also m aking t he FirstName and LastName propert ies prot ect ed. The O/ O Mapper should have no problem s accessing t he prot ect ed PM propert ies using reflect ion, so it will be able t o m ap t o t hem from t he Dom ain Model's FirstName and LastName propert ies, but only t he FullName propert y will be exposed t o t he client by t he PM obj ect . I n fact , t he O/ O Mapper could even writ e direct ly t o t he privat e fields of t he PM obj ect s, so you wouldn't have t o im plem ent t he prot ect ed propert ies at all indeed, t he m apper probably should access t he privat e fields direct ly in order t o avoid t riggering side effect s in t he propert y get t ers and set t ers t hat should only be invoked when client code accesses t he propert ies, not when t he fram ework want s t o m ove dat a. However, som e O/ O Mappers m ay offer advanced feat ures ( such as Lazy Loading) t hat depend on propert ies being t here so t hat access t o t hem can be int ercept ed, and so you m ay want t o keep t he propert ies around for t hat reason. The following code list ing shows a PM obj ect t hat relies on O/ O Mapping. //Presentation Model object that relies on O/O Mapping namespace MyCompany.MyProject.Presentation { public class EmployeeView { private int id; private decimal salary;
private string firstName; private string lastName; public EmployeeView() {} public int Id { get { return this.id; } set { this.id = value; } } public decimal Salary { get { return this.salary; } set { this.salary = value; } } protected string FirstName { get { return this.firstName; } set { this.firstName = value; } } protected string LastName { get { return this.lastName; } set { this.lastName = value; } } public string FullName { get { return this.firstName + " " + this.lastName; } set { string[] names = value.Split(" ".ToCharArray() , 2); this.firstName = names[0]; this.lastName = names[1]; } } } }
Of course, what ever you do in a fram ework, you could also do m anually. I f you were t o copy t he dat a m anually from Dom ain Model t o PM wit hout t he aid of an O/ O Mapping fram ework, in order t o writ e t o t he prot ect ed propert ies you could eit her use reflect ion or creat e short cut m et hods for accessing your propert ies by nam e. Som e code using t he second approach m ight look like t he following: //Move data between Presentation and Domain Model without //transformation
Employee employee = employeeRepository.GetEmployeeById(42); EmployeeView employeeView = new EmployeeView(); employeeView.Id = employee.Id; employeeView.Salary = employee.Salary; //We have to use a method that lets us access protected //properties by name to write to the protected methods. //Alternatively, we could use reflection. employeeView.SetPropertyValue("FirstName", employee.FirstName); employeeView.SetPropertyValue("LastName", employee.LastName);
Using t his approach, t he t ask of m oving t he dat a bet ween t he PM and t he Dom ain Model becom es st raight forwardalm ost t rivial, had it not been for t he pesky reference propert ies.
Managing Relationships When we t ake relat ionships bet ween t he obj ect s int o account , t he t ask becom es m ore difficult again. Obj ect graphs ( a group of obj ect s t hat are all int erconnect ed via relat ionships) are pot ent ially very large, and if asking t he O/ O Mapper t o fill a PM Employee obj ect from a DM Employee obj ect also result s in t he filling up of a few hundred relat ed obj ect s, we m ay have effect ively killed perform ance in our applicat ion. Anot her issue is t hat you have t o rem em ber t o ret urn PM obj ect s from PM reference propert ies. For exam ple, when I read t he AssignedToProject propert y of a PM Employee obj ect , I want a PM Project obj ect back, not a DM Project obj ect . Consider a naïve im plem ent at ion of a Wrapper obj ect wit h a reference propert y as shown in t he following: //Naïve implementation of reference property in wrapper object namespace MyCompany.MyApplication.Presentation { public class EmployeeWrapper { private Employee employee; public EmployeeWrapper (Employee employee) { this.employee = employee; } public Project AssignedToProject { get { return this.employee.Project; } set { this.employee.Project = value; } } } }
The problem here is t hat t he Project propert y of t he PM EmployeeWrapper obj ect will ret urn a DM Project obj ect rat her t han a PM ProjectWrapper obj ect . This is usually not at all what we want , and so we have t o t ake care t o writ e our PM reference propert ies in t he fashion shown here: //Slightly less naïve implementation of reference property
//in wrapper object namespace MyCompany.MyApplication.Presentation { public class EmployeeWrapper { private Employee employee; public EmployeeWrapper (Employee employee) { this.employee = employee; } public ProjectWrapper AssignedToProject { get { return new ProjectWrapper(this.employee.Project); } set { this.employee.Project = value.GetDomainObject(); } } public Employee GetDomainObject() { return employee; } } }
Not e t he GetdomainObject( ) m et hod on t he ProjectWrapper class used in t he AssignedToProject( ) set t er m et hod. When you writ e t o t he AssignedToProject propert y of t he PM EmployeeWrapper obj ect , you pass a ProjectWrapper obj ect t o t he set t er, but we have t o pass a Dom ain Model Project obj ect t o t he Project propert y of t he wrapped Employee obj ect . Thus we need a way t o get t o t he required Project obj ect from t he passed in ProjectWrapper obj ect , and t he GeTDomainObject( ) m et hod fills t his r ole. So when we im plem ent reference propert ies, we suddenly have t o supply a m et hod for get t ing at t he Dom ain Model obj ect referenced int ernally by a PM obj ect som et hing t hat m ight not be needed ot herwise. For com plet eness, and in order t o be able t o im plem ent a corresponding AssignedEmployees propert y in t he ProjectWrapper class, we have also provided a GetdomainObject( ) m et hod on t he EmployeeWrapper class. But even t his isn't really enough. Looking crit ically at t he code, we can't help but not ice t hat each t im e we read from t he AssignedToProject propert y, a new ProjectWrapper obj ect is creat ed. Assum ing t he I dent it y Mapping in our Dom ain Model layer is working, each new ProjectWrapper obj ect creat ed when reading t he sam e propert y over and over will wrap t he sam e DM Project obj ect , and so we shouldn't run t he risk of dat a inconsist ency and corrupt ion, but it is hardly ideal nonet heless.
I n t he end, we m ight want a full- blown solut ion t hat could handle I dent it y Mapping in t he PM as well, m aking sure t hat t here are never t wo different PM inst ances represent ing t he very sam e Dom ain Model inst ance around in a session. The code in t he AssignedToProject propert y would t hen have t o be rewrit t en so t hat inst ead of creat ing a new inst ance of t he ProjectWrapper obj ect it self, it would ask a PM Reposit ory wit h an I dent it y Map for t he ProjectWrapper obj ect . That m eans t hat t he EmployeeWrapper obj ect will need a reference t o t he ProjectRepository obj ect . Say goodbye t o t he com fort ing sim plicit y t hat has hit hert o graced t he Present at ion Model. The end result is t hat t he whole slew of fascinat ing and ent ert aining horrors and hardships t hat we rem em ber from m anaging reference propert ies in t he Dom ain Layer will rear t heir ugly heads again. One way of " solving" t hese issues wit h reference propert ies is t o sim ply avoid reference propert ies in your PM obj ect s, using " flat t ened" obj ect s inst ead t hat expose only prim it ive propert ies, quit e possibly from referenced obj ect s as well. See t he following code list ing. //"Flattened" Presentation Model object, exposing only primitive //properties namespace MyCompany.MyApplication.Presentation { public class EmployeeWrapper { private Employee employee; public EmployeeWrapper(Employee employee) { this.employee = employee; } public int Id { get { return this.employee.Id; } set { this.employee.Id == value; } } public string AssignedToProjectName { get { return this.employee.Project.Name; } set { this.employee.Project.Name = value; } } } }
This approach is oft en very useful, especially because m any UI cont rols are designed for showing t ables of rows, and flat t ened obj ect s adapt well t o t his paradigm . But whenever you are int erest ed in represent ing navigable, deep st ruct ures rat her t han j ust grids of st uff, t he flat t ened approach falls short . However, you m ay oft en find yourself com plem ent ing a fully part it ioned PM wit h som e flat obj ect s specifically writ t en for som e grid in your applicat ion. " Flat t ened" PM obj ect s provide a good exam ple of how t he PM oft en looks very different from t he Dom ain Model and, of course, t he m ore different t hey are, t he m ore m ot ivat ed we are t o have t wo separat e m odels inst ead of j ust adding t he feat ures needed by t he PL t o t he Dom ain Model.
Matters of State When m aking t he choice bet ween Mapping and Wrapping, special at t ent ion should be paid t o t he difference in how st at e is handled in t he t wo approaches. When wrapping your Dom ain Model obj ect s, only one inst ance of t he dat a is used by t he applicat ion. I f you have t wo views showing t he sam e em ployee at t he sam e t im e, and you updat e t he em ployee's nam e in one view, t he single inst ance of t he dat a in t he wrapped obj ect is updat ed. I f t he second view is t hen refreshed, it should display t he updat ed dat a, because it is also m apping direct ly t o t he sam e wrapped, updat ed obj ect . When m apping your PM obj ect s t o your Dom ain Model obj ect s, however, you could pot ent ially have several different set s of PM obj ect s represent ing t he sam e Dom ain Model obj ect , but wit h different st at e in t hem . Even if you have an I dent it y Map for your PM, you m ay have t wo different PM Employee classes t hat look slight ly different for different present at ion purposes, in which case t he I dent it y Map is of no help at all. This could lead t o conflict s. On t he ot her hand, it also opens up t he possibilit y of m ore sophist icat ed, disconnect ed dat a m anagem ent . For exam ple, a m apped set of PM obj ect s m ay be worked wit h for a long t im e in isolat ion in som e wizard. At t he last st ep of t he wizard, t he user could decide t o com m it t he work t o t he Dom ain Model ( and t hen t o com m it t he updat ed Dom ain Model t o t he dat abase) or t o discard t he changes. Had t he user been working wit h a set of PM obj ect s t hat direct ly wrapped t he Dom ain Model obj ect s, t his opt ion of canceling t he changes would not have been available because t he Dom ain Model obj ect s would have been cont inuously updat ed when t he user worked in t he wizard! Cert ainly, t he opt ion of canceling so t hat t he changes aren't forwarded t o t he dat abase is t here even wit hout t he PM, assum ing your Dom ain Model support s disconnect ed operat ions ( for exam ple, by using a Unit of Work [ Fowler PoEAA] ) . However, if you press cancel at t he end of a wizard t hat works direct ly wit h t he Dom ain Model, even if your dat abase is spared from t he changes, your Dom ain Model will st ill have been changed so t hat when you present it in t he next screen of your applicat ion, t he changes are visible. I f you are prepared t o t hrow away your Dom ain Model if t he user cancels, t here is no problem . There is also no problem if t he Unit of Work support s full rollback of t he st at e in t he Dom ain Model. But if you are writ ing a rich client where you int end t o let t he Dom ain Model live for t he scope of t he applicat ion ( for exam ple, you don't want t o t hrow t he DM away if t he user cancels) and your Unit of Work does not support full rollbacks, you m ay well run int o t his issue. I n som e sit uat ions, you'll find t hat wrapping best suit s your needs, while m apping works bet t er in ot her sit uat ions. Som et im es you'll use bot h in t he sam e applicat ion, perhaps using wrapping as t he default approach but using m apping j ust for wizards or ot her " bat ch j obs" t hat should be possible t o discard wit hout com m it t ing.
Final Thoughts Som et im es you're j ust plain lucky. Whenever you're able t o present your Dom ain Model t o t he user right away ( or wit h a m inor am ount of support ive hacks) you should t hank your lucky st ars, because t here's no get t ing around t he fact t hat connect ing a PM t o a Dom ain Model can be quit e headacheinducing. The headache is significant ly reduced if you decide t o go wit h " flat t ened" PM obj ect s, because m ost of
t he m ore severe issues arise direct ly as a result of t rying t o m anage reference propert ies. The choice bet ween wrapping and m apping is influenced by a num ber of fact ors, including how st at e is m anaged and t he opport unit ies for reducing t he am ount of boilerplat e code in your applicat ions via code generat ion ( wrapping) or via using an O/ O Mapping fram ework ( m apping) . By t rying t o keep st ruct ural t ransform at ions out side t he scope of any fram eworks or code generat ors you em ploy ( inst ead placing all such advanced t ransform at ion logic in code wit hin your PM classes and hiding non- t ransform ed m em bers in t he PM by m aking t hem prot ect ed) , you great ly reduce t he com plexit y required by such fram eworks/ code generat ors and t hereby im prove your chances of writ ing som et hing useful yourself or finding som et hing available online. I f you doubt t hat all t his ext ra archit ect ural overhead in t he form of PMs, O/ O Mappers, and t ransform at ion logic m et hods is really necessaryfine, perhaps you're lucky enough t o find yourself wit h a present able Dom ain Model. The bot t om line, however, is t hat you should really, really t ry t o avoid m odifying t he Dom ain Model in order t o suit it t o t he needs of t he PLdon't t ry t o m ake your Dom ain Model work double as a PM unless you can get away wit h t hat wit hout changing t he Dom ain Model. As soon as you recognize t hat t here is a difference bet ween t he way you want t o represent your Dom ain Model in t he core of t he applicat ion and t he way you want t o present it t o t he user, you recognize t he need for a PMat least as long as you want t o follow t he advice of never burdening t he Dom ain Model wit h PL aspect s. I am slight ly fanat ic about keeping m y Dom ain Model obj ect s com plet ely oblivious of PL aspect s. This m eans t hat if m y Dom ain Model could be dat a bound t o direct ly and if only som e at t ribut es were added t o t he propert ies ( like at t ribut es for specifying cat egory and default value for binding t o t he propert y grid) , I 'll refrain from adding t hose PL at t ribut es t o m y Dom ain Model obj ect s. I nst ead I 'll opt for com plem ent ing m y Dom ain Model wit h a PM even if it is in every det ail exact ly t he sam e as t he Dom ain Model, except for t he at t ribut es. I f you can't ident ify yourself wit h t he lunat ic fringe dem ographic, t hough, perhaps you'll st op short of t hat . But one reason I keep doing t hat is t hat as soon as t he PM is t hereas well as t he support ing infrast ruct ure for Wrapping or Mapping t o t he Dom ain Model I usually find plent y of opport unit y for refining t he present at ional aspect s of t he PM, and soon enough t he t wo m odels will begin t o becom e m ore and m ore different , m aking it easier and easier t o m ot ivat e com plem ent ing t he Dom ain Model wit h a PM. Thanks, Mat s!
Summary I t hink m y friends j ust described how you can benefit from t he Dom ain Model again t hrough t he discussions of present at ion services. Even t hough you focus on t he core wit h t he Dom ain Model, t hat 's not t he sam e as saying t hat t he present at ion service is less im port ant . On t he cont rary! The im port ance of t he present at ion service is one of t he reasons why you should work hard on t he Dom ain Model. By m oving out as m any t hings as possible from t he PL t hat are j ust dist ract ions in t hat cont ext , it is easier or m ore possible t o creat e a first - class UI .
Epilogue So we have been t hinking about an applicat ion in som et hing of a DDD- ish way. The book was pret t y m uch st ruct ured t he sam e way, by which I m ean t he following:
1 . First we t ry t o collect t he requirem ent s and underst and t he problem . 2 . Next we t hink about which approach t o use for st ruct uring t he m ain logic. According t o Fowler, we can choose from Transact ion Script , Table Module, and Dom ain Model [ Fowler PoEAA] . I f t he requirem ent s signal t hat t his is a com plex ( regarding behavior and/ or relat ionships) and longlived proj ect , we will probably choose Dom ain Model. 3 . Then we need a cert ain st yle for t he Dom ain Model, not only t o avoid com m on t raps, but also t o creat e som et hing really powerful. That 's where DDD com es in. We t ry t o set t le t he Ubiquit ous Language [ Evans DDD] . We work hard wit h t he Dom ain Model, t rying t o m ake it knowledge rich. 4 . Aft er t hat it 's t im e t o t hink about t he dist ract ions: t he required infrast ruct ure. The m ost t ypical problem t o consider is how t o deal wit h persist ence. I f possible, obj ect relat ional m apping is a popular choice for DDD proj ect s. ( I nst ead of t he som ewhat overloaded t erm " infrast ruct ure," let 's describe t his as it 's all about m apping t he m odel t o ot her t iers, such as present at ion, persist ence, and int egrat ion services.) 5 . Finally, needless t o say, t here are lot s of ot her t hings t o consider, such as how t o deal wit h present at ion. Not t hat I t hink it 's a wat erfall process, not at all, but a book is sequent ial by nat ure and consequent ly m ore oft en t han not t he descript ion will be as well. I t 's im port ant t o rem em ber and st ress t he it erat ive and increm ent al st yle of developm ent t hat is required t o m it igat e risk and lead t o a successful proj ect . OK, it 's over. This book, t hat is. But as Dan said, it 's j ust t he beginning. Just one m ore t hing: I st art ed t his book t alking about how I value " lagom " ( som et hing like not t oo m uch and not t oo lit t le, but rat her balanced) . Has t he book been " lagom " ? Well, I t hink t o som e it has been ext rem e in som e direct ions, and perhaps " lagom " in ot hers. What is balanced is all in t he eye and in t he cont ext of t he observer.
Part V: Appendices There are t wo appendices providing furt her exam ples of Dom ain Model st yles and a cat alog wit h an overview of pat t erns.
Th e a ppe n dice s a r e a bou t ot h e r st yle s a n d colle ct ion s.
Appendix A Ot her Dom ain Model St yles Appendix B Cat alog of Discussed Pat t erns
Appendix A. Other Domain Model Styles There are lot s of variat ions on how t he Dom ain Model pat t ern is used. I asked a couple of friends of m ine t o describe t heir favorit e ways of applying Dom ain Models. As t he input , t hey got t he requirem ent s list ings from Chapt er 4, " A New Default Archit ect ure," and you will find t heir answers here in Appendix A. [ 1] I 'll repeat t hose requirem ent s here for you: [1]
Please note that when they received the input, the idea was to require an application server in the scenario, something I changed later on for Chapter 4.
1 . List cust om ers by applying a flexible and com plex filt er. The cust om er support st aff needs t o be able t o search for cust om ers in a very flexible m anner. They need t o use wildcards on num erous fields, such as nam e, locat ion, st reet address, reference person, and so on. They also need t o be able t o ask for cust om ers wit h orders of a cert ain kind, orders of a cert ain size, orders for cert ain product s, and so on. What we're t alking about here is a full- fledged search ut ilit y. The result is a list of cust om ers, each wit h a cust om er num ber, cust om er nam e, and locat ion. 2 . List t he orders when looking at a specific cust om er. The t ot al value for each order should be visible in t he list , as should t he st at us of t he order, t ype of order, order dat e, and nam e of reference person. 3 . An order can have m any different lines. An order can have m any order lines, where each line describes a product and t he num ber of it em s of t hat product t hat has been ordered. 4 . Concurrency conflict det ect ion is im port ant . I t 's alright t o use opt im ist ic concurrency cont rol. That is, it 's accept ed t hat when a user is not ified aft er he or she has done som e work and t ries t o save, t here will be a conflict wit h a previous save. Only conflict s t hat will lead t o real inconsist encies should be considered as conflict s. So t he solut ion needs t o decide on t he versioning unit for cust om ers and for orders. ( This will slight ly affect som e of t he ot her feat ures.) 5 . A cust om er m ay not owe us m ore t han a cert ain am ount of m oney. The lim it is specific per cust om er. We define t he lim it when t he cust om er is added init ially, and we can change t he am ount lat er on. I t 's considered an inconsist ency if we have unpaid orders of a t ot al value of m ore t han t he lim it , but we allow t hat inconsist ency t o happen in one sit uat ion and t hat is if a user decreases t he lim it . Then t he user t hat decreases t he lim it is not ified, but t he save operat ion is allowed. However, it 's not allowed t o add an order or change an order so t hat t he lim it is exceeded. 6 . An order m ay not have a t ot al value of m ore t han one m illion SEK. This lim it ( unlike t he previous one) is a syst em - wide rule. ( SEK is t he Swedish currency, but t hat 's not im port ant .)
7 . Each order and cust om er should have a unique and user- friendly num ber. Gaps in t he series are accept able. 8 . Before a new cust om er is considered OK, his or her credit will be checked wit h a credit inst it ut e. That is, t he lim it discussed previously t hat is defined for a cust om er will be checked t o see if it 's reasonable. 9 . An order m ust have a cust om er; an order line m ust have an order. There m ust not be any orders wit h an undefined cust om er. The sam e goes for order lines, t hey m ust belong t o an order. 1 0 . Saving an order and it s lines should be at om ic. To be honest , I 'm not act ually sure t hat t his feat ure is necessary. I t m ight be alright if t he order is creat ed first and t he order lines are added lat er on, but I want t he rule t o be like t his so t hat we have a feat ure request relat ed t o t ransact ional prot ect ion. 1 1 . Orders have an accept ance st at us t hat is changed by t he user. This st at us can be changed by users bet ween different values ( such as t o approved/ disapproved) . To ot her st at us values, t he change is done im plicit ly by ot her m et hods in t he Dom ain Model. First up is Mat s Helander wit h t he variat ion he calls " Obj ect - orient ed dat a m odel, sm art service layer, and docum ent s." Here goes.
Object-Oriented Data Model, Smart Service Layer, and Documents By Mat s Helander Som et im es, during dark wint er night s, I recall faint ly what life was like in t he days before t he Dom ain Model. I t is at t im es like t hose t hat I pour a lit t le cognac int o m y hot chocolat e and t hrow an ext ra log on t he fire. The evolut ion of t he archit ect ure in m y applicat ions, finally leading up t o t he use of t he Dom ain Model, followed a pat t ern som e readers m ay feel fam iliar wit h. I 'm going t o out line t his evolut ion quickly, as it helps put m y current approach for using t he Dom ain Model int o cont ext .
In the Beginning I st art ed out writ ing client - server applicat ions where all t he applicat ion code was st uffed int o t he client , which would t alk direct ly t o t he dat abase. When I st art ed writ ing Web applicat ions, t hey consist ed of ASP pages wit h all t he applicat ion code in t hem including t he code for calling t he dat abase. Because t he result of t his t ype of applicat ion archit ect ure is, wit hout except ion, a horrific m ess whenever t he applicat ion grows beyond t he com plexit y of a " Hello World" exam ple, I soon learned t o fact or out m ost of t he code from t he present at ion layer and t o put it in a Business Logic Layer ( BLL) inst ead. I m m ediat ely, t he applicat ions becam e easier t o develop, m ore m aint ainable, and as a bonus t he BLL could be m ade reusable bet ween different present at ion layers. I t should also be not ed t hat in t hose days, it could m ake a big difference t o t he perform ance and scalabilit y of a Web applicat ion t o have t he bulk of it s code com piled in a com ponent . The next st ep was t o lift out all t he code responsible for com m unicat ing wit h t he dat abase from t he BLL and put it in a layer of it s own, t he Dat a Access Layer ( DAL) . I recall t hat I was slight ly surprised t o see t hat t he DAL was oft en subst ant ially larger t han t he BLLm eaning m ore of m y applicat ion logic dealt wit h dat abase com m unicat ion t han wit h act ual business logic. At t his t im e, I was alm ost at t he point where t he Dom ain Model would ent er int o t he equat ion. The PL would t alk t o t he BLL t hat would t alk t o t he DAL t hat would t alk t o t he dat abase. However, all dat a from t he dat abase was passed around t he applicat ion in t he form of Recordset s, t he in- m em ory represent at ion of rows in t he dat abase ( see Figure A- 1) .
Figu r e A- 1 . Cla ssic, pr e - D om a in M ode l la ye r e d a pplica t ion a r ch it e ct u r e
Som et im es, working wit h t he dat a in it s relat ional form is j ust what you want , and in t hose cases t he archit ect ure described earlier st ill fit s t he bill nicely. But , as I was becom ing aware of, relat ional dat a st ruct ures aren't always ideal for t he business logic t o work wit h.
Object-Orientation and Relational Data Structures From som e work I had done wit h an obj ect - orient ed dat abase from Com put er Associat es called Jasm ine, I had com e t o underst and t hat an obj ect - orient ed dat a st ruct ure was quit e oft en a lot easier t o work wit h when developing t he business logic t han t he corresponding relat ional st ruct ure. Relat ionships bet ween ent it ies are sim pler, for one t hing. I n t he case of m any- t o- m any relat ionships, t his is especially so, because t he relat ional dat a st ruct ure requires an addit ional t able whereas no addit ional class is required in t he obj ect - orient ed dat a st ruct ure. Collect ion propert ies are anot her case where an addit ional t able is used in t he relat ional m odel but no ext ra class is required in t he obj ect - orient ed dat a st ruct ure. Furt herm ore, obj ect - orient ed dat a st ruct ures are t ype safe and support inherit ance.
The Domain Model and Object-Relational Mapping The problem was t hat OO dat abases weren't t hat com m on. Most proj ect s I encount ered st ill revolved around t he relat ional dat abase. This is where Obj ect - Relat ional Mapping ent ered t he pict ure. What Obj ect - Relat ional Mapping does is t o let you t ake an obj ect - orient ed dat a st ruct ure and m ap it t o t he t ables in a relat ional dat abase. The advant age is t hat you will be able t o work wit h your obj ect - orient ed dat a st ruct ure as if working wit h an OO dat abase like Jasm ine, while in realit y you're st ill using a relat ional dat abase t o st ore all your dat a. Furt herm ore, you can add business logic t o t he classes of t he obj ect - orient ed dat a st ruct ure in t he form of business m et hods. As you m ay have guessed, t he obj ect - orient ed dat a st ruct ure wit h business m et hods t hat I 'm t alking about is t hat 's right t he Dom ain Model. By learning about Obj ect - Relat ional Mapping I could finally t ake t he st ep t oward including t he Dom ain Model in m y applicat ion archit ect ure. I nsert ed bet ween t he BLL and t he DAL, t he Dom ain Model Layer now allowed m y business logic t o work wit h an obj ect - orient ed dat a st ruct ure ( see Figure A- 2) .
Figu r e A- 2 . Upda t e d, la ye r e d a pplica t ion a r ch it e ct u r e in clu din g D om a in M ode l
This m eant t hat a BLL m et hod t hat had previously been working wit h a Recordset cont aining a row from t he Employees t able would now inst ead work wit h an Employee Dom ain Model class. I gained t ype safet y, and t he code becam e short er and easier t o readnot t o m ent ion t hat I could navigat e m y dat a st ruct ure during developm ent using Microsoft 's I nt elliSense feat ure! Ever since, I 've been a devot ed user of t he Dom ain Model and I haven't looked backexcept during t hose cold, dark wint er night s. As I m ent ioned, anot her advant age wit h t he Dom ain Model is t hat it let s you dist ribut e your business logic over t he Dom ain Model classes. I 've experim ent ed a lot wit h where t o put m y business logic. I 've shift ed bet ween put t ing m ost of it in t he Business Logic Layer t o put t ing m ost of it in t he Dom ain Model Layer and back again.
Service Layer I 've also com e t o prefer t he t erm Service Layer [ Fowler PoEAA] t o Business Logic Layer, as it bet t er covers what I 'm doing at t im es when m ost of m y business logic is in t he Dom ain Model Layer. When m ost of m y business logic is in t he Service Layer I see no reason t o swit ch back t o t he t erm Business Logic Layer, t hough I som et im es refer t o it as a " t hick" Service Layer t o dist inguish it from a m ore convent ionally t hin Service Layer. These days I alm ost always put m ost of t he business logic in a " t hick" Service Layer. While som e OO purist s m ight argue t hat st ruct ure and behavior should be com bined, every so oft en I find it pract ical t o keep t he business logic- relat ed behavior in t he Service Layer. The m ain reason for t his is t hat in m y experience, business rules governing t he behavior of t he Dom ain Model will usually change at a different pace, and at different t im es, from t he rules governing t he st ruct ure of t he Dom ain Model. Anot her reason is t hat it easily let s you reuse t he Dom ain Model under different set s of business rules. Norm ally, t he only business m et hods I would put on t he Dom ain Model obj ect s t hem selves are ones t hat I believe are fundam ent al enough t o be valid under any set of business rules and are probably going t o change at t he sam e t im e and pace as t he st ruct ure of t he Dom ain Model. Wit h m ost of m y business logic in t he Service Layer, t he applicat ion archit ect ure is nicely prepared for t he st ep int o t he world of SOA. But before we go t here and t ake a look at how I would t ackle Jim m y's exam ple applicat ion, I 'd j ust like t o st ress anot her part icular benefit of t his approach.
Combining Things As I m ent ioned earlier, t he obj ect - orient ed dat a st ruct ure offered by t he Dom ain Model isn't t he perfect fit for every operat ion you'll want t o perform . There are reasons why relat ional dat abases are st ill so popular and why m any people prefer t o use Obj ect - Relat ional Mapping before going wit h an act ual OO dat abase. For m any t asks, t he relat ional dat a st ruct ure and t he set - based operat ions offered by SQL are t he perfect fit .
I n t hese cases, slapping m et hods ont o t he Dom ain Model obj ect s t hat will act ually be perform ing set based operat ions direct ly on t he relat ional dat a st ruct ures m ay not seem like a very nat ural way t o go. I n cont rast , let t ing Service Layer m et hods t hat access t he dat abase direct ly ( or via t he DAL) co- exist side by side wit h m et hods t hat operat e on t he Dom ain Model is no st ret ch at all. Convert ing a Service Layer m et hod from using one approach t o t he ot her is also easily accom plished. I n short , I 'm free t o im plem ent each Service Layer m et hod as I see fit , bypassing t he Dom ain Model Layer where it is not useful as well as t aking full advant age of it where it is useful ( which, in m y experience, happens t o be quit e oft en) .
Jimmy's Application and SOA Having said t hat , let 's ret urn t o Jim m y's applicat ion and t he brave new world of SOA. So far I have only really described what m y applicat ion archit ect ure looks like on t he server, which m ay be enough for a Web applicat ion, but now we're going t o deal wit h a Rich Client , and I 'd like t o focus on applicat ion servers as a variat ion t o t he rest of t he book. I n cases where a lot , if not all, of t he business logic has been dist ribut ed over t he Dom ain Model, a t em pt ing approach is t o t ry t o export bot h dat a and behavior packaged t oget her in t his way t o t he Rich Client by giving it access t o t he Dom ain Model. Wit h t he archit ect ure I 'm using, where m ost of t he behavior is found in t he Service Layer, a m ore nat ural approach is t o expose t he behavior and t he dat a separat ely. Concret ely, t he Service Layer is com plem ent ed wit h a Web Service façade ( a t ype of t he Rem ot e Façade pat t ern [ Fowler PoEAA] ) , which is j ust a t hin layer for exposing t he business logic m et hods in t he Service Layer as Web Services. The Rich Client com m unicat es wit h t he server by calling t he Web Services, exchanging XML docum ent s cont aining serialized Dom ain Model obj ect s. For our exam ple applicat ion, t his m eans t hat each t im e t he Rich Client needs new inform at ion it will call a Web Service on t he server t hat will ret urn t he inform at ion in t he form of an XML docum ent . To begin wit h, let 's t ake t he case of list ing cust om ers m at ching a filt er. I would begin by creat ing a GetCustomersByFilter() Service Layer m et hod im plem ent ing t he act ual filt ering. The Service Layer m et hod would t ake t he filt er argum ent s and ret urn t he m at ching Dom ain Model obj ect s. Then I 'd add a GetCustomersByFilter() Web Service exposing t he Service Layer m et hod t o t he Rich Client . The Web Service accept s t he sam e filt er argum ent s and passes t hem on in a call t o t he Service Layer m et hod. The Dom ain Model obj ect s ret urned by t he Service Layer m et hod would t hen be serialized t o an XML docum ent t hat would finally be ret urned by t he Web Service. The Rich Client can t hen use t he inform at ion in t he XML docum ent t o display t he list of cust om ers t o t he user ( see Figure A- 3) .
Figu r e A- 3 . Rich Clie n t Applica t ion Se r ve r la ye r e d a pplica t ion a r ch it e ct u r e in clu din g D om a in M ode l
I t is com plet ely up t o t he Rich Client applicat ion developers if t hey want t o creat e a client - side Dom ain Model t hat t hey can fill wit h t he dat a from t he XML docum ent . They m ay also decide t o st ore t he inform at ion for offline use. One way is t o j ust save t he XML docum ent s t o disk. Anot her is t o persist t he client - side Dom ain Models t o an offline dat abase on t he client m achine. Not e t hat t he event ual client - side Dom ain Model doesn't have t o m at ch t he Dom ain Model on t he server. Accordingly, if t he client - side Dom ain Model is persist ed t o an offline dat abase, t he client - side dat abase will presum ably follow t he design of t he client - side Dom ain Model and t hus doesn't have t o look like t he server- side dat abase.
N ot e One im port ant difference regarding t he schem a of t he server- side dat abase and t he client side dat abase is t hat if a server- side dat abase t able uses aut om at ically increasing ident it y fields, t he corresponding client - side dat abase t able norm ally shouldn't . Say t hat a new em ployee is creat ed on t he server. As a new row is insert ed in t he Employees t able it is aut om at ically given a value in it s aut o- increasing I D colum n. The em ployee is t hen serialized and sent t o t he client , which fills a client - side Dom ain Model wit h t he dat a and st ores it in t he client - side dat abase for offline use. The new row in t he client - side Employees t able should not be assigned wit h a new I Dt he I D t hat was assigned t o t he em ployee in t he server- side dat abase should be used. This m eans t hat t he propert ies for t he I D colum ns m ay be different on t he client and t he server.
The point here is t hat t he developers of t he Rich Client applicat ion are free t o decide whet her or not t hey want t o use a Dom ain Model at all, and if t hey want one t hey are free t o design it as t hey see fit . The com m unicat ion bet ween client and server is 100% docum ent - orient ed, and no assum pt ions about t he Dom ain Models on eit her side are m ade by t he ot her. For exam ple, m aybe t he Rich Client calls Web Services of m any different com panies, all capable of ret urning list s of cust om ers but all wit h slight ly different fields in t heir XML docum ent s. One way t o deal wit h t his would be t o creat e a " superset " client - side Customer Dom ain Model obj ect wit h propert ies for all t he different fields t hat were ret urned from all t he com panies called. Next , we want t he user of t he Rich Client applicat ion t o be able t o look at t he orders belonging t o a part icular cust om er. The unique I D of each cust om er was of course included in t he XML docum ent ret urned by t he first Web Service, so we know t he I D of t he cust om er in which we're int erest ed. What I have t o do is t o im plem ent a Web Service t hat t akes t he I D of a cust om er and ret urns t he orders for t hat cust om er. Again, I begin by creat ing a GetOrdersByCustomerID() Service Layer m et hod t hat accept s t he I D of a cust om er and ret urns t he Dom ain Model Order obj ect s belonging t o t he cust om er.
I 'll t hen proceed by adding a GetOrdersByCustomerID() Web Service t hat t akes a cust om er I D, passes it along t o t he Service Layer m et hod, and finally serializes t he ret urned order obj ect s int o an XML docum ent t hat can be ret urned by t he Web Service. However, in t his case, som e ext ra at t ent ion has t o be paid t o t he XML serializat ion. We want t he XML docum ent t o include t he t ot al value for each order, but t he Order Dom ain Model obj ect doesn't cont ain any TotalValue propert y because t he t ot al value of an order is not st ored in any field in t he dat abase. Rat her, t he t ot al value is calculat ed whenever it is needed by adding t he values of t he order lines t oget her. The value of each order line, in it s t urn, is calculat ed by m ult iplying t he price t hat t he purchased product had when it was bought by t he num ber of it em s bought . Because t he order lines aren't included in t he XML docum ent sent t o t he client , t he client applicat ion can't calculat e t he t ot al values for t he orders, which m eans t hat t he calculat ion has t o be done on t he server and t he result has t o be included in t he XML docum ent . The m et hods for calculat ing t he value of an order line and t he t ot al value for an order are good exam ples of t he t ype of m et hod t hat m any people would prefer t o put on t he OrderLine and Order Dom ain Model obj ect s but t hat I prefer t o put in t he Service Layer.
Service Layer Design This m ay be a good t im e t o m ent ion t hat I usually organize m y Service Layer around t he sam e ent it ies t hat are used in t he Dom ain Model, rat her t han by t ask, which is ot herwise also popular. This m eans t hat if I have an Employee obj ect in t he Dom ain Model Layer, I 'll have an EmployeeServices class in t he Service Layer. For services t hat don't need any t ransact ion cont rol, I also usually im plem ent t he Service Layer m et hods as st at ic m et hods so t hat t hey can be called wit hout inst ant iat ing any obj ect from t he Service Layer classes. I n t he case at hand, I would t hus place a st at ic GetValue() m et hod on t he OrderLineServices Service Layer class and a st at ic GetTotalValue() m et hod on t he OrderServices class. These m et hods will accept t he obj ect s t hey should work wit h as param et ers. This m eans I won't be calling a m et hod on t he Order obj ect it self in t he following fashion: myOrder.GetTotalValue();
I nst ead I 'll be calling a Service Layer m et hod, like t his: OrderServices.GetTotalValue(myOrder);
I t is t his kind of procedural synt ax t hat has som e OO purist s j um ping in t heir seat s, hollering, and t hrowing rot t en fruit , but I t hink t hat t he benefit s t hat follow from separat ing t he behavior from t he st ruct ure of t he Dom ain Model are well wort h t he slight ly uglier synt ax. Of course, t he service m et hods don't have t o be st at ic, and som et im es t hey can't befor exam ple, if t hey should init iat e a declarat ive t ransact ions or if you want t o configure your SL classes using Dependency I nj ect ion ( see Chapt er 10, " Design Techniques t o Em brace" ) . But when st at ic m et hods will do, I usually m ake t hem st at ic sim ply because t hat m eans less code ( no need t o wast e a line of code inst ant iat ing a SL obj ect every t im e I want t o call a service) . Because it
m akes for less code, I 'll cont inue using st at ic service m et hods in m y exam ples. The point is t hat I don't really lose t he opport unit y for organizing m y m et hods pret t y m uch as nicely as if I had put t hem on t he Dom ain Model classes. Because for every Dom ain Model class I have a corresponding Service Layer class, I 'm able t o part it ion m y business logic in j ust t he sam e way as if I had dist ribut ed it over t he act ual Dom ain Model. The benefit com es when t he rules for GetTotalValue() st art changing even t hough t he underlying dat a st ruct ure doesn't changesuch as when suddenly a syst em wide rule should be im plem ent ed st at ing t hat an order m ay not have a t ot al value exceeding one m illion SEK. You can t hen j ust m odify t he relevant Service Layer m et hods wit hout having t o recom pile or even t ouch your Dom ain Model Layer t hat can rem ain t est ed and t rust ed. Ret urning t o t he GetOrdersByCustomerID() Web Service, I j ust add a call t o t he Service Layer OrderServices.GetTotalValue() m et hod t o which I pass each order during t he serializat ion rout ine and add a field t o t he XML docum ent for holding t he result . This provides us wit h a good exam ple of how t he Dom ain Model on t he client , should t he client applicat ion developer choose t o im plem ent one, can differ from t he server- side Dom ain Model. The client - side Dom ain Model Order obj ect m ight well include a propert y for t he t ot al value as present ed in t he XML docum ent a propert y t hat t he server- side Dom ain Model Order obj ect doesn't have. Following t his approach, we m ay decide t o include even m ore fields in our XML docum ent wit h values t hat have been calculat ed by Service Layer m et hods. For inst ance, consider a syst em - wide rule specifying t hat a cust om er m ay not owe m ore t han a cert ain am ount of m oney. The precise am ount is individual and calculat ed by som e special Service Layer m et hod. When designing t he XML docum ent t o ret urn from t he GetCustomersByFilter() Web Service, you m ay want t o include a field for t he m axim um am ount each cust om er can owe, or at least a flag st at ing if t he cust om er is over t he lim it . This goes t o dem onst rat e t hat t he docum ent s offered by t he server don't have t o m at ch t he Dom ain Modelcom m unicat ion bet ween client and server is ent irely docum ent - orient ed. Looking at what we have at t he m om ent , t he user of t he Rich Client applicat ion should be able t o view filt ered list s of cust om ers and t o view t he list of orders belonging t o each cust om er. Next , we want t o let t hem check out t he order lines belonging t o a specific order. There's no news in how t his is im plem ent ed: The client calls a Web Service wit h t he I D of t he order, t he Web Service forwards t he call t o a Service Layer m et hod and serializes t he result s t o XML. By now, we know t he drill for let t ing t he client fet ch dat a. What about let t ing t he client subm it dat a?
Submitting Data Let 's say we want t he user t o be able t o updat e t he st at us of an order from " Not inspect ed" t o " Approved" or " Disapproved." At first glance it m ay seem like a st raight forward request , but we're really opening a Pandora's Box of concurrency problem s here as we m ove from read- only t o readwrit e access. So let 's begin by st icking wit h t he first glance and not look any closer for a lit t le while. How would we do it t hen? The first t hing t o do is t o creat e an UpdateOrderStatus() Service Layer m et hod and a corresponding Web Service. The m et hods should accept t he I D of t he order and t he new order st at us as param et ers and ret urn a bool value t elling you if t he operat ion com plet ed successfully. ( We won't go int o any advanced cross- plat form except ion handling hereeven t hough it 's a whole bunch of fun! )
So far, so good; t he Service Layer m et hod is easy enough t o im plem ent , and t he Web Service is j ust a wrapper, forwarding t he call t o t he Service Layer m et hod. Now let 's look at t he problem s we're going t o run int o. The problem s arise if t wo users t ry t o updat e t he st at us of t he sam e order at t he sam e t im e. Say t hat user A fet ches an order, not es t hat it is " Not inspect ed" and goes on t o inspect it . Just a lit t le lat er, user B fet ches t he sam e order, not es t he sam e t hing, and also decides t o inspect it . User A t hen not es a sm all problem wit h t he order, causing her t o m ark t he order as " Disapproved" in her following call t o t he UpdateOrderStatus() Web Service. Just aft er user A has updat ed t he st at us of t he order, user B proceeds t o call t he sam e Web Service for updat ing t he st at us of t he sam e order. However, user B hasn't not iced t he problem wit h t he order and want s t o m ark t he order as " Approved." Should user B be able t o overwrit e t he st at us t hat A gave t he order and t hat is now st ored in t he dat abase? A st andard answer would be " No, because user B didn't know about t he changes user A had m ade." I f user B had known about t he change t hat user A m ade and st ill decided t o override it , t he answer would be " Yes" because t hen B could be t rust ed not t o overwrit e A's dat a by m ist ake. So t he challenge becom es t his: How do we keep t rack of what B, or any ot her client , " knows" ? One approach is t o keep t rack of t he original values from t he dat abase for updat es fields and t hen at t he t im e when t he m odified dat a is saved t o t he dat abase, check if t he original values m at ch t hose t hat are in t he dat abase now. I f t he values in t he dat abase have changed, t he client didn't know about t hese changes and t he client 's dat a should be rej ect ed. This approach is called opt im ist ic concurrency. Let 's look at a very sim ple way t hat opt im ist ic concurrency could be im plem ent ed for t he case at hand. To begin wit h, t he client applicat ion has t o be coded so t hat when t he user changes t he st at us of an order, t he st at us t hat was originally ret urned from t he dat abase via t he XML docum ent is st ill kept around. Then t he UpdateOrderStatus() Web Service and t he Service Layer m et hod wit h t he sam e nam e have t o be m odified t o accept an addit ional param et er for t he original order st at us value. Thus, when t he client calls t he Web Service, t hree param et ers are passed: t he I D for t he order, t he new order st at us, and t he original order st at us. Finally, t he UpdateOrderStatus() Service Layer m et hod should be updat ed t o include a check if t he current st at us for t he order in t he dat abase m at ches t hat which was passed t o t he param et er for t he original order st at us, execut ing t he updat e only if t he values m at ch. This version of opt im ist ic concurrency, m at ching t he original values of updat ed fields wit h t hose in t he dat abase, will give good perform ance and scalabilit y, but it can be a bit cum bersom e t o im plem ent , at least in a Rich Client scenario. An alt ernat ive way of using opt im ist ic concurrency is t o add a versioning field t o t he t ables in your dat abase. Som et im es even st rat egies like opt im ist ic concurrency won't work, so dom ain- specific solut ions have t o be applied in t hose cases. A norm al t hem e is t o use som e sort of check- in/ out syst em , where t he user m arks an obj ect as " checked out " or " locked" when it is fet ched for writ e access, prevent ing ot her users from updat ing it unt il she is done. Ot her, m ore sophist icat ed schem es include m erging of concurrent ly updat ed dat a. The only way t o decide on what st rat egy is appropriat e in t he part icular case is t o ask t he dom ain specialist t hat is, t he person underst anding t he business im plicat ions of t hese decisionswhat behavior is desired. There's no general way t o select a concurrency st rat egy. Even so, I 'm guessing t hat t he appropriat e way t o go in t his case would be t o use opt im ist ic
concurrency, wit h or wit hout a versioning field. The point is t hat it is st ill up t o som ebody who knows what t he expect ed behavior is for t heir business t o decide. Perhaps t he biggest problem wit h t he approach we have used here is t hat we have asked t he client t o keep t rack of t he original values for us. This is probably asking a bit t oo m uch of t he client applicat ion developer, so a bet t er alt ernat ive m ight be t o st ore t he original values on t he server in a Session variable or a sim ilar const ruct .
How Fine-Grained? Anot her t hing t o consider is whet her t he service is t oo fine- grained. Rat her t han providing a separat e Web Service for every field of an order t hat can be updat ed, could we, perhaps, im plem ent a single UpdateOrder() Web Service inst ead? I n t his case, rat her t han providing a param et er for each updat eable field, t he Web Service could accept an XML docum ent represent ing t he updat ed order. That is, inst ead of t his: [WebMethod]public bool UpdateOrder(int orderID, int orderStatus , DateTime orderDate, int customerID)
you could use t his: [WebMethod] public bool UpdateOrder(string xmlOrder)
I n t his way t he client bot h receives and subm it s dat a in t he form of XML docum ent s. This st rict ly docum ent - orient ed approach helps in keeping t he Web Services API coarse- grained and avoiding overly " chat t y" conversat ions bet ween t he client and t he server ( see Figure A- 4) .
Figu r e A- 4 . Ove r vie w of fu ll a pplica t ion a r ch it e ct u r e
A Few Words About Transactions Finally, a not e on t ransact ions. As I m ent ioned earlier, I usually m ake m y Service Layer m et hods st at ic whenever I don't need t ransact ions, but in t he case of a business applicat ion m anaging orders, I 'm inclined t o believe t ransact ions will be part of t he solut ion. This im plies t hat , for exam ple, t he UpdateOrderStatus() m et hod should be t ransact ional. What t his m eans is t hat inst ead of m aking t his m et hod st at ic I t urn it int o an inst ance- level m et hod and m ark it wit h t he [AutoComplete] .NET at t ribut e. Because I 'm also going t o m ark t he whole class t hat t he m et hod belongs t o wit h t he [transaction] at t ribut e and let it inherit from ServicedComponent, I decide t o lift t his m et hod out of t he OrderServices class and int o a new class called OrderServicesTx, where " Tx" st ands for " t ransact ional." And t hat 's it . All m y UpdateOrderStatus() Web Service has t o do now is t o creat e an inst ance of t he OrderServicesTx class and call t he UpdateOrderStatus() on t hat obj ect inst ead of calling t he old st at ic
m et hod on t he OrderServices class. The execut ion of t he OrderServicesTx.UpdateOrderStatus() will use dist ribut ed t ransact ions t hat will be aut om at ically com m it t ed if t he m et hod finished wit hout except ions and rolled back ot herwise. The Service Layer is, in m y opinion, t he perfect place t o enforce t ransact ion cont rol in your applicat ion. I t is also a great place for put t ing your securit y checks, logging, and ot her sim ilar aspect s of your applicat ion. Not e t hat all t he m et hods in t he Service Layer don't have t o be organized around t he ent it ies from t he Dom ain Model layer. Just because I 'll have EmployeeServices, CustomerServices, and so on, t hat doesn't m ean I won't also have LoggingServices and SecurityServices classes in m y Service Layer as well.
Summary My preferred st yle is t o put m ost of t he business logic in t he Service Layer and let it operat e on t he obj ect - orient ed dat a st ruct ure represent ed by t he Dom ain Model. The com m unicat ion bet ween client and server is com plet ely docum ent - orient ed, and no at t em pt t o export t he Dom ain Model t o t he client or even t o let t he client access t he server- side Dom ain Model ( for exam ple, via Rem ot ing) is m ade. I hope I 've been able t o give som e idea about how I use t he Dom ain Layer and whyas well as how I would go about designing Jim m y's applicat ion.
The Database Model Is the Domain Model By Frans Boum a To work wit h dat a on a sem ant ic basis, it 's oft en useful t o specify general definit ions of t he elem ent s a given port ion of logic will work wit h. For exam ple, an order syst em works wit h, am ong ot her elem ent s, Order elem ent s. To be able t o define how t his logic works, a definit ion of t he concept Order is pract ical: We will be able t o describe t he funct ionalit y of t he syst em by specifying act ions on Order elem ent s and supply wit h t hat a definit ion of t hat elem ent Order. This Order elem ent cont ains ot her elem ent s ( values like t he OrderID and ShippingDate) and has a t ight connect ion wit h anot her elem ent , OrderRow , which in t urn also cont ains ot her elem ent s. You can even say t hat Order cont ains a set of OrderRow elem ent s like it cont ains value elem ent s. I s t here a difference bet ween t he cont ainm ent of t he value OrderID and t he cont ainm ent of t he set of OrderRow elem ent s? The answer t o t his quest ion is im port ant for t he way t he concept of Order is im plem ent ed furt her when t he order syst em is realized wit h program code.
The Entity's Habitat Looking at t he relat ional m odel developed by Dr. E.F. Codd [ Codd Relat ional Model] , t hey should be seen as t wo separat ed elem ent s: Order and OrderRow , which have a relat ionship, and t he elem ent s it self form a r elat ion based on t he values t hey cont ain, like OrderID. However, looking at t he Dom ain Model, pioneered by Mart in Fowler [ Fowler PoEAA] and ot hers, it doesn't have t o be t hat way. You could see t he OrderRow elem ent s in a set as a value of Order and work wit h Order as a unit , including t he OrderRow elem ent s.
What's an Entity? I n 1970, Dr. Pet er Chen defined t he concept ent it y for his Ent it y Relat ionship Model [ Chen ER] , which builds on t op of Codd's Relat ional Model. The concept of t he ent it y is very useful in defining what Order and OrderRow look like in a relat ional m odel. Chen defined Ent it y as " En t it y and En t it y se t . Let e denot e an ent it y which exist s in our m inds. Ent it ies are classified int o different ent it y set s such as EMPLOYEE, PROJECT, and DEPARTMENT. There is a predicat e associat ed wit h each ent it y set t o t est whet her an ent it y belongs t o it . For exam ple, if we know an ent it y is in t he ent it y set EMPLOYEE, t hen we know t hat it has t he propert ies com m on t o t he ot her ent it ies in t he ent it y set EMPLOYEE. Am ong t hese propert ies is t he afore- m ent ioned t est pr edicat e." By using t he Ent it y Relat ionship Model, we're able t o define ent it ies like Order and OrderRow and place t hem int o a relat ional m odel, which defines our dat abase. Using Eric Evans' definit ion of Ent it y, however, we are far away from t he relat ional m odel, which I t hink com es down t o t he following definit ion: " An obj ect t hat is t racked t hrough different st at es or even across different im plem ent at ions." The im port ant difference bet ween Evans' definit ion and Chen's definit ion of an ent it y is t hat Chen's definit ion is t hat of an abst ract elem ent ; it exist s wit hout having st at e or even a
physical represent at ion. Wit h Evans, t he ent it y physically exist s; it 's an obj ect , wit h st at e and behavior. Wit h t he abst ract ent it y definit ions, we're not influenced by t he cont ext in which an ent it y's dat a is used, as t he int erpret at ion of t he dat a of an ent it y is not done by t he ent it y it self ( as t here is no behavior in t he ent it y) but by ext ernal logic. To avoid m isint erpret at ions, we're going t o use t he definit ion of Dr. Pet er Chen. The reason for t his choice is because it defines t he abst ract t erm for t hings we run int o every day, bot h physical it em s and virt ual it em s, wit hout looking at cont ext or cont ained dat a as t he definit ion is what 's im port ant . I t is t herefore an ideal candidat e t o describe elem ent s in relat ional m odels like Customer or Order. A physical Customer is t hen called an ent it y inst ance.
Where Does an Entity Live? Every applicat ion has t o deal wit h a phenom enon called st at e. St at e is act ually a t erm t hat is t oo generic. Most applicat ions have several different kinds of st at e: user st at e and applicat ion st at e are t he m ost im port ant ones. User st at e can be seen as t he st at e of all obj ect s/ dat a st ores t hat hold dat a on a per- user basis ( t hat is, have " user scope" ) at a given t im e T. An exam ple of user st at e is t he cont ent s of t he Session obj ect of a given user in an ASP.NET applicat ion at a given m om ent . Applicat ion st at e is different from user st at e; it can be seen as t he st at e of all obj ect s/ dat a st ores t hat hold dat a on an applicat ion scope basis. An exam ple can be t he cont ent s of a dat abase shared am ong all users of a given Web applicat ion at a given m om ent . I t is not wise t o see t he user st at e as a subset of t he applicat ion st at e: When t he user is in t he m iddle of a 5- st ep wizard, t he user st at e holds t he dat a of st ep one and t wo; however, not hing in t he applicat ion st at e has changed; t hat will be t he case aft er t he wizard is com plet ed. I t is very im port ant t o define where an ent it y inst ance lives: in t he applicat ion st at e or in t he user st at e. I f t he ent it y inst ance lives in t he user st at e, it 's local t o t he user owning t he user st at e, and ot her users can't see t he ent it y and t herefore can not use it . When an ent it y inst ance is creat ed, like an order is physically creat ed in t he aforem ent ioned order syst em , it is living inside t he act ual applicat ion; it is part of t he applicat ion st at e. However, during t he order creat ion process, when t he user fills in t he order form , for exam ple, t he order is not act ually creat ed; a t em porary set of dat a is living inside t he user's st at e, which will becom e t he order aft er it is finalized. We say t he ent it y inst ance get s per sist ed when it is act ually creat ed in t he applicat ion st at e. You can have different t ypes of applicat ion st at e: a shared, in- m em ory syst em t hat holds ent it y inst ances, or you can have a dat abase in which ent it y inst ances are st ored. Most soft ware applicat ions dealing wit h ent it y inst ances use som e kind of persist ent st orage t o st ore t heir ent it y inst ance dat a t o m ake it survive power out ages and ot her t hings causing t he com put er t o go down, losing it s m em ory cont ent s. I f t he applicat ion uses a persist ent st orage, it is likely t o call t he dat a in t he persist ent st orage t he act ual applicat ion st at e: when t he applicat ion is shut down, for exam ple, for m aint enance, t he applicat ion doesn't lose any st at e: no order ent it y inst ance is lost , and it is st ill available when t he applicat ion is brought back up. An ent it y inst ance in m em ory is t herefore a m irror of t he act ual ent it y inst ance in t he persist ent st orage, and applicat ion logic uses t hat m irror t o alt er t he act ual ent it y inst ance t hat lives in t he persist ent st orage.
Mapping Classes onto Tables Versus Mapping Tables onto Classes O/ R Mapping deals wit h t he t ransform at ion bet ween t he relat ional m odel and t he obj ect m odel: t hat is, t ransform ing obj ect s int o ent it y inst ances in t he persist ent st orage and back. Globally, it can be defined as t he following: A field in a class in t he obj ect m odel is relat ed t o an at t ribut e of an ent it y in t he relat ional m odel and vice versa.
A chicken- egg problem arises: what follows what ? Do you first define t he ent it y classes ( classes r epr esent ing ent it y definit ions) , like an Order class represent ing t he Order ent it y) and creat e relat ional m odel ent it ies wit h at t ribut es using t hese classes, or do you define a relat ional m odel first and use t hat relat ional m odel when you define your ent it y classes? As wit h alm ost everyt hing, t here is no clear " t his is how you do it " answer t o t hat quest ion. " I t depends" is probably t he best answer t hat can be given. I f you're following t he Dom ain Model, it is likely you st art wit h dom ains, which you use t o define classes, som e probably in an inherit ance hierarchy. Using t hat class m odel, you sim ply need a relat ional m odel t o st ore t he dat a, which could even be one t able wit h a prim ary key consist ing of t he obj ect I D, a binary blob field for t he obj ect , and a couple of m et adat a elem ent s describing t he obj ect . I t will t hen be nat ural t o m ap a class ont o elem ent s in t he relat ional m odel aft er you've m ade sure t he relat ional m odel is const ruct ed in a way t hat it serves t he obj ect m odel best . I f you st art wit h t he relat ional m odel and you const ruct an E/ R m odel, for exam ple, it is likely you want t o m ap an ent it y in your relat ional m odel ont o a class. This is different from t he approach of t he Dom ain Model, for inst ance, because t he relat ional m odel doesn't support inherit ance hierarchies: you can't m odel a hierarchy like Person < Em ployee < Manager such t hat it also represent s a hierarchy. I t is, of course, possible t o creat e a relat ional m odel t hat can be sem ant ically int erpret ed as an inherit ance hierarchy; however, it doesn't represent an inherit ance hierarchy by definit ion. This is t he fundam ent al difference bet ween t he t wo approaches. St art ing wit h classes and t hen working your way t o t he dat abase uses t he relat ional m odel and t he dat abase j ust as a place t o st ore dat a, while st art ing wit h t he relat ional m odel and working your way t owards classes uses t he classes as a way t o work wit h t he relat ional m odel in an OO fashion. As we've chosen t o use Chen's way of defining ent it ies, we'll use t he approach of defining t he relat ional m odel first and working our way up t o classes. Lat er on in t he " The I deal World" sect ion we'll see how t o bridge t he t wo approaches.
Working with Data in an OO Fashion Ent it y- represent ing classes are t he developer's way t o define ent it ies in code, j ust as a physically im plem ent ed E/ R m odel wit h t ables defines t he ent it ies in t he persist ent st orage. Using t he O/ R Mapping t echnique discussed in t he previous sect ion, t he developer is able t o m anipulat e ent it y inst ances in t he persist ent st orage using in- m em ory m irrors placed in ent it y class inst ances. This is always a bat ch- st yle process, as t he developer works disconnect ed from t he persist ent st orage. The cont rolling environm ent is t he O/ R Mapper, which cont rols t he link bet ween ent it y inst ances in t he persist ent st orage and t he in- m em ory m irrors inside ent it y class inst ances. A developer m ight ask t he O/ R Mapper t o load a given set of Order inst ances int o m em ory. This result s in, for each Order inst ance in t he persist ent st orage, a m irror inside an ent it y class inst ance. The developer is now able t o m anipulat e each ent it y inst ance m irror t hrough t he ent it y class inst ance or t o display t he ent it y inst ance m irrors in a form or offer it as out put of a service. Manipulat ed ent it y inst ance m irrors have t o be persist ed t o m ake t he changes persist ent . From t he developer's point of view, t his looks like saving t he m anipulat ed ent it y inst ance dat a inside t he obj ect s t o t he persist ent st orage, like a user saves a piece of t ext writ t en in a word processor t o a file. The O/ R Mapper is perform ing t his save act ion for t he developer. But because we're working wit h m irrors, t he act ual act ion t he O/ R Mapper is perform ing is updat ing t he ent it y inst ance in t he persist ent st orage wit h t he changes st ored in t he m irror received from t he developer's code. The relat ionships bet ween t he ent it ies in t he relat ional m odel are represent ed in code by funct ionalit y provided by t he O/ R Mapper. This allows t he developer t o t raverse relat ionships from one ent it y inst ance t o anot her. For exam ple, in t he Order syst em , loading a Customer inst ance int o m em ory allows t he developer t o t raverse t o t he Customer's Order ent it y inst ances by using funct ionalit y
provided by t he O/ R Mapper, be it a collect ion obj ect inside t he Customer obj ect or a new request t o t he O/ R Mapper for Order inst ances relat ed t o t he given Customer inst ance. This way of working wit h ent it ies is rat her st at ic: const ruct ing ent it ies at runt im e t hrough a com binat ion of at t ribut es from several relat ed ent it ies does not result in ent it y- represent ing classes, as classes have t o be present at com pile t im e. This doesn't m ean t he ent it y inst ances const ruct ed at runt im e t hrough com binat ions of at t ribut es ( for exam ple, t hrough a select wit h an out er j oin) can't be loaded int o m em ory; however, t hey don't represent a persist able ent it y, but rat her a virt ual ent it y. This ext ra layer of abst ract ion is m ost ly used in a read- only scenario, such as in report ing applicat ions and read- only list s, where a com binat ion of at t ribut es from relat ed ent it ies is oft en required. An exam ple of a definit ion for such a list is t he com binat ion of all at t ribut es of t he Order ent it y and t he " com pany nam e" at t ribut e from t he Customer ent it y. To successfully work wit h dat a in an OO fashion, it is key t hat t he funct ionalit y cont rolling t he link bet ween in- m em ory m irrors of ent it y inst ances and t he physical ent it y inst ances offers enough flexibilit y so t hat report ing funct ionalit y and list s of com bined set of at t ribut es are definable and loadable int o m em ory wit hout needing t o use anot her applicat ion j ust for t hat m ore dynam ic way of using dat a in ent it y inst ances.
Functional Research as the Application Foundation To efficient ly set up t he relat ional m odel, t he m appings bet ween ent it y definit ions in t he relat ional m odel and ent it y represent ing classes and t hese classes it self, it is key t o reuse t he result s from work done early in t he soft ware developm ent proj ect , t he Funct ional Research Phase. This phase is t ypical for a m ore classical approach t o soft ware developm ent . I n t his phase, t he funct ional requirem ent s and syst em funct ionalit y are det erm ined, defined in an abst ract way and docum ent ed. Over t he years, several t echniques have been defined t o help in t his phase; one of t hem is NI AM [ Halpin/ Nij ssen Concept ual Schem a] , which is furt her developed by T.A. Halpin [ Halpin I MRD] t o Obj ect Role Modeling ( ORM) . NI AM and ORM m ake it easy t o com m unicat e funct ional research findings wit h t he client in easy t o underst and sent ences like " Cust om er has Order" and " Order belongs t o Cust om er." These sent ences are t hen used t o define ent it ies and relat ionships in an abst ract NI AM/ ORM m odel. Typically, a visual t ool is used for t his, such as Microsoft Visio.
The Importance of Functional Research Results The advant age of m odeling t he research findings wit h t echniques like NI AM or ORM is t hat t he abst ract m odel bot h docum ent s t he research findings during t he funct ional research phase and at t he sam e t im e it is t he source for t he relat ional m odel t he applicat ion is going t o work wit h. Using t ools like Microsoft Visio, a relat ional m odel can be generat ed by generat ing an E/ R m odel from an NI AM/ ORM m odel, which can be used t o const ruct a physical relat ional m odel in a dat abase syst em . The m et adat a form ing t he definit ion of t he relat ional m odel in t he dat abase syst em can t hen be used t o generat e classes and const ruct m appings. The advant age of t his is t hat t he class hierarchy t he developers work wit h has a t heoret ical basis in t he research perform ed at t he st art of t he proj ect . This m eans t hat when som et hing in t he design of t he applicat ion changes, such as a piece of funct ionalit y, t he sam e pat h can be followed: t he NI AM m odel changes, t he relat ional m odel is adj ust ed wit h t he new E/ R m odel creat ed wit h t he updat ed NI AM m odel, and t he classes are adj ust ed t o com ply t o t he new E/ R m odel. The ot her way around is also t rue: t o find a reason for code const ruct s t he developer has t o work wit h. For exam ple, for code const ruct s t o t raverse relat ionships bet ween ent it y inst ance obj ect s, you only have t o follow back t he pat h from t he class t o t he funct ional research result s and t he t heoret ical basis for t he code const ruct s is revealed. This st rong connect ion bet ween a t heoret ical basis and act ual code is key t o a successful, m aint ainable soft ware syst em .
Functional Processes as Data Consumers and Location of Business Logic As t he real ent it y definit ions live in t he relat ional m odel, inside t he dat abase, and in- m em ory inst ances of ent it ies are j ust m irrors of real inst ances of ent it ies in t he dat abase, t here is no place for behavior, or Business Logic rules, in t hese ent it ies. Of course, adding behavior t o t he ent it y classes is easy. The quest ion is whet her t his is logical, when ent it y classes represent ent it y definit ions in t he relat ional m odel. The answer depends on t he cat egory of t he Business Logic you want t o add t o t he ent it y as behavior. There are roughly t hree cat egories: At t ribut e- orient ed Business Logic Single- ent it y- orient ed Business Logic Mult i- ent it y- orient ed Business Logic At t ribut e- orient ed Business Logic is t he cat egory t hat cont ains rules like OrderId > 0. These are very sim ple rules t hat act like const raint s placed on a single ent it y field. Rules in t his cat egory can be enforced when an ent it y field is set t o a value. The cat egory of single- ent it y- orient ed Business Logic cont ains rules like ShippingDate >= OrderDate, and t hose also act like const raint s. Rules in t his cat egory can be enforced when an ent it y is loaded int o an ent it y obj ect in m em ory, saved int o t he persist ent st orage, or t o t est if an ent it y is valid in a given cont ext . The m ult i- ent it y- orient ed Business Logic cat egory cont ains rules spanning m ore t han one ent it y: for exam ple, t he rule t o check if a Customer is a Gold Cust om er. To m ake t hat rule t rue, it has t o consult Order ent it ies relat ed t o t hat Customer and Order Detail ent it ies relat ed t o t hese Order ent it ies. All t hree cat egories have dependencies on t he cont ext t he ent it y is used in, alt hough not all rules in a given cat egory are cont ext - dependent rules. At t ribut e- orient ed Business Logic is t he cat egory wit h t he m ost rules t hat are not bound t o t he cont ext t he ent it y is used in, and it is a good candidat e t o add t o t he ent it y class as behavior. Single- ent it y- orient ed Business Logic) is oft en not a good candidat e t o add t o t he ent it y class as behavior, because m uch of t he rules in t hat cat egory, which are used t o m ake an ent it y valid in a given cont ext , can and will change when t he ent it y is used in anot her cont ext . Rules in t he m ult i- ent it y- orient ed Business Logic cat egory span m ore t han one ent it y and are t herefore not placeable in a single ent it y, besides t he fact t hey're t oo bound t o t he cont ext in which t hey're used.
Pluggable Rules To keep an ent it y usable as a concept t hat isn't bound t o a given cont ext , t he problem wit h cont ext bound Business Logic rules in t he cat egory at t ribut e- orient ed Business Logic and t he cat egory singleent it y- orient ed Business Logic can be solved wit h pluggable rules. Pluggable rules are obj ect s t hat cont ain Business Logic rules and t hat are plugged int o an ent it y obj ect at runt im e. The advant age of t his is t hat t he ent it y classes are not t ied t o a cont ext t hey are used in but can be used in any cont ext t he syst em design asks for: j ust creat e per- cont ext a set of pluggable rules obj ect s, or even m ore per- ent it y, and depending on t he cont ext st at e, rules can be applied t o t he ent it y by sim ply set t ing an obj ect reference. The processes t hat decide which rules obj ect s t o plug int o ent it ies are t he processes m aint aining t he cont ext t he ent it y is used in: t he processes represent ing act ual business processes t hat are called funct ional processes.
Functional Processes I n t he previous sect ion, The I m port ance of Funct ional Research Result s," t he funct ional research phase was described, and t he point was m ade concerning how im port ant it is t o keep a st rong link bet ween researched funct ionalit y and act ual im plem ent at ion of t hat funct ionalit y. Oft en a syst em has t o aut om at e cert ain business processes, and t he funct ional research will describe t hese processes in an abst ract form . To keep t he link bet ween research and im plem ent at ion as t ight as possible, it 's a com m on st ep t o m odel t he act ual im plem ent at ion aft er t he abst ract business process, result ing in classes t hat we'll call funct ional processes because t hey're m ore or less dat a- less classes wit h sole funct ionalit y. The funct ionalit y processes are t he ideal candidat es in which t o im plem ent m ult i- ent it y- orient ed Business Logic rules. I n our exam ple of t he Gold Cust om er, a process t o upgrade a Customer t o a Gold Cust om er can be im plem ent ed as a funct ional process t hat consum es a Customer ent it y obj ect and it s Order ent it y obj ect s, updat es som e fields of t he Customer ent it y, and persist s t hat Customer ent it y aft er t he upgrade process is com plet e. Furt herm ore, because funct ional processes act ually perform t he st eps of a business process, t hey are also t he place where a cont ext is present in which ent it ies are consum ed and t he only correct place t o decide which rules t o plug int o an ent it y obj ect at a given t im e T for a given cont ext st at e.
The Ideal World Using NIAM/ORM for Classes and Databases For m ost people, realit y is not always in sync wit h what we expect t o be an ideal world, and everyday soft ware developm ent is no except ion t o t hat . I n t he funct ional research paragraph, physical relat ional m odel m et adat a were used t o produce m appings and classes t o get t o t he ent it y class definit ions for t he developer t o work wit h. A m ore ideal approach would be if t he NI AM/ ORM m odel could also be used t o generat e ent it y classes direct ly, avoiding a t ransform at ion from m et adat a t o class definit ion. I t would m ake t he ult im at e goal, where t he design of t he applicat ion is as usable as t he applicat ion it self, appear t o be one st ep closer. When t hat ideal world will be a realit y, or even if it will be a realit y, is hard t o say as a lot of t he fact ors t hat influence how a soft ware proj ect could be m ade a success can be found in areas out side t he world of com put er science. Nevert heless, it 's int erest ing t o see what can be accom plished t oday, wit h t echniques developed t oday, like m odel- driven soft ware developm ent .
Pragmatism and the Nontraditional Approach By I ngem ar Lundberg When Jim m y first asked m e course." I m ust adm it I was What do I have t o say? Can can m y .NET archit ect ure fit
if I was willing t o writ e t his " guest segm ent " I im m ediat ely said " Yes, of flat t ered. Not surprisingly, I becam e doubt ful j ust a short while lat er. I be precise enough t o fit it in a very lim it ed space? And above all, how int o t he Dom ain Model concept ?
I will at t em pt t o give you a general descript ion of t he archit ect ure I 've built for m y form er em ployer, which, wit hout sham e, I will call m y archit ect ure from now on. I t is however not t he archit ect ure I 'm using nowadays. I will also address som e of t he issues in Jim m y's exercise t hat I find int erest ing wit h regards t o t he st rengt hs and weaknesses in m y archit ect ure. I can't possibly give you t he whole pict ure in t his lim it ed space, but because t he int ent ion of t his sect ion is t o show you t hat t here is m ore t han one way t o skin a cat , I hope t his brief descript ion is sufficient enough t o fulfill it s purpose. When I t alk about how t hings are done, please rem em ber t hat t he cont ext is t his part icular archit ect ure and not a general dogm a.
Background and History I need t o bore you wit h som e background and hist ory in an at t em pt t o explain why t hings in m y archit ect ure are t he way t hey are. I want t o point out t wo or t hree t hings t hat have been t he m ain influence on t he st at e of t hings. First , you need t o know t hat before .NET I had designed a MTS/ COM+ archit ect ure. Wit h t he arrival of .NET, t he ( hom e- brewed) concept s from t he old archit ect ure weren't m ore t han a few years old, and t hus were barely " cem ent ed" in people's ( developers') m inds and t oo expensive t o lose. [ 2] The second t hing you need t o know is t hat m y archit ect ure isn't necessarily " t he best " I could t hink of. I hope it is a good balance bet ween st at e of t he art design/ developm ent t echniques, t he skills of t he personnel using it , and t he qualit y/ cost rat io request ed. [2]
If you work as a "technology leader" in an IT department, I'm sure you've noticed that when you are ready to move on to the next thing, people around you haven't really grasped what you've been talking about lately.
Overview of the Architecture What t hen are t he concept s of m y archit ect ure? Those of you fam iliar wit h MTS/ COM+ have been t horoughly t aught t he concept s of st at eless OO program m ing ( a cont radict ion in t erm s?) . The process obj ect [ Ewald TxCOM+ ] is a st at eless obj ect represent ing a single, ent ire Business Obj ect ( BO) in one inst ance. I t cont ains t he full set of operat ions for one or a set of business obj ect s of a part icular business obj ect t ype. The st at elessness grant s you t his possibilit y. The basis in t he archit ect ure is a fram ework of collaborat ing classes. I t is usable by it self, but t o be really effect ive it has a com panion t ool, BODef ( for a snapshot of a part of it , look ahead t o Figure A11 ) , where you define your business obj ect s and generat e boilerplat e code. The code generat or is m ore t han a wizard because it let s you generat e t im e aft er t im e wit hout losing m anual code addit ions. I t uses t he Generat ion Gap pat t ern [ Vlissides Pat t ern Hat ching] t o accom plish t his. I n
Figure A- 5, you can see one ( part ial) definit ion of t he BOs of t he problem at hand.
Figu r e A- 5 . BOs for t h e e x a m ple [View full size image]
The " st andard" way of dividing applicat ion classes is shown in Figure A- 6. All packages/ assem blies, except t hose m arked wit h < < fram ework> > , are applicat ion specific. The BoSupport assem bly is supposed t o be available not only in t he m iddle t ier but also in Rem ot ing client s. The rich client required in Jim m y's problem challenge is going t o com m unicat e wit h t he m iddle t ier via Rem ot ing. [ 3] [3]
Jimmy's note: As I mentioned in the introduction to this appendix, an application server was in the requirement from the beginning but was removed later on.
Figu r e A- 6 . Ove r a ll a r ch it e ct u r e
The Service assem bly should cont ain at least one façade obj ect t hat im plem ent s som e basic int erfaces t o support basic CRUD, IBvDBOReq being t he m ost im port ant ; see Figure A- 7.
Figu r e A- 7 . Ar ch it e ct u r e for t h e fa ca de [View full size image]
This façade isn't generat ed; inst ead, you inherit all base funct ionalit y ( from FacadeBase) , including t he im plem ent at ion of t he m ent ioned int erfaces. When it com es t o persist ent st orage, t he presence of a relat ional dat abase syst em ( RDBMS) is as sure as t he presence of t he file syst em it self at t he sit e. I n fact , t he RDBMS was an unspoken requirem ent , and if I want ed proper backup handling of m y applicat ions dat a, I 'd bet t er use one. This is an im port ant st at em ent . This is why I don't bot her t o pret end t hat we didn't use an RDBMS.
Retrieval of Data I t is possible t o ret urn dat a from t he m iddle t ier t o t he client in any form at : DataTable, DataSet, st rongly t yped DataSet, XML or serialized obj ect graphs, t o m ent ion a few ( or m aybe m ost ) . The sim plest and m ost com m on ret urn form at ( in m y case) is t he DataTable. To ret urn a bunch of " business obj ect dat a," you sim ply ret urn each as a row.
Business Object Placeholder A row in t he DataTable can easily be convert ed t o a business obj ect placeholder, BoPlaceholder . The BoPlaceholder class, locat ed in t he BoSupport assem bly, is usable in it s own right , but it 's also t he base class of specific business obj ect placeholders. The specific placeholders, one of t hem being POrderPh ( purchase order) in Figure A- 8, are generat ed by BODef. The Impl class is generat ed each t im e you choose generat e. The leaf class is only generat ed once, which is pret t y m uch t he essence of t he Generat ion Gap pat t ern.
Figu r e A- 8 . Usa ge of BoPla ce h olde r [View full size image]
You m ight t hink of t hese placeholder obj ect s as inform at ion carriers or dat a t ransfer obj ect s, but t hey m ight be used in m ore com plex behavior scenarios. And, yes, t he BoPlaceholder derivat es are in t he BoSupport assem bly, which is supposed t o be present at bot h t he m iddle t ier and in t he client t ier ( t he rich/ fat client ) . You can ret urn a DataTable from t he m iddle t ier t o t he client , leaving it t o t ransform from DataRow inst ances t o BoPlaceholder inst ances. You can also ret urn a set ( ArrayList, for inst ance) of BoPlaceholders t o be used direct ly in t he client . BoPlaceholder is serializable. However, rem em ber t hat you can find loads of sam ples out t here of how t o dat abind a DataTable, but sam ples of dat a bound BoPlaceholders, alt hough possible, are rare.
Process Objects The final execut ion and enforcem ent of t he business rules is a j ob for t he core business obj ect s, also known as t he process obj ect s. These obj ect s are st at eless and very short - lived. You can see t he POrder process obj ect in Figure A- 9 and see t hat t hey follow t he generat ion gap pat t ern. These obj ect s are im plem ent ed in t he Core assem bly. Because I consider aut horizat ion t o be a core part of business rules, it 's no surprise t hat roles are specified on ( public) m et hods of t hese obj ect s.
Figu r e A- 9 . Loca t in g cu st om r u le s in POr de r , t h e Ta k e Or de r ( ) m e t h od in t h is e x a m ple [View full size image]
Despit e t he fact t hat business rule enforcem ent is t he responsibilit y of t he core obj ect s, one of t hem being POrder in Figure A- 9, it is st ill very possible t o im plem ent t he business rules in t he BoSupport assem bly and t hus m ake t hem available in t he rich client . However, when I do t hat I always m ake sure I double- check t he rules in t he process obj ect s before persist ing t he changes. You see, it is very possible t hat a client has overridden a rule, say t he upper order lim it , perhaps via reflect ion, before subm it t ing it t o t he façade obj ect in t he m iddle t ier. The process obj ect s are oft en handed BoPlaceholders and are " hidden" behind a façade obj ect . The process obj ect and t he placeholder, such as POrder and POrderPh , are concept ually part s of t he sam e ( logical) business obj ect .
Metadata As you m ight have not iced in som e of t he previous figures, t here's plent y of m et a- dat a available in t he fram ework classes. A m ore com plet e pict ure of t he m et a- dat a, alt hough not explained in dept h, is found in Figure A- 10 . The m et adat a can give you som e dynam ics, bot h on dat a st orage handling and UI handling.
Figu r e A- 1 0 . A com ple t e pict u r e of t h e m e t a da t a [View full size image]
SQL st at em ent s in t he m iddle t ier are dynam ically const ruct ed from m et a- dat a, and you get a fairly good and very well- proven im plem ent at ion. Should you discover bot t lenecks, you have t he freedom t o im plem ent t he SQL handling your own way, such as by using st ored procedures.
Jimmy's Order Application Exercise Let 's t urn our int erest t o Jim m y's problem , t he order- handling applicat ion. I im agine t hat t he user of t he order placem ent funct ionalit y ( applicat ion) is som eone speaking wit h ( serving) a cust om er over t he phone. Let 's call t his person an order t aker. The fat client sort of im plies t his, if you see m y point . One could easily im agine t he desire for an e- com m erce applicat ion, if not now t hen in t he fut ure. I don't t hink t his has a big im pact on t he design should we go t hat way, but st ill, m y int erpret at ion of t he scenario will affect , for inst ance, what roles we need in t he syst em . I have t he roles User and Adm in. User can do m ost st uff and Adm in can do j ust about anyt hing. ( Wit h an e- com m erce app you m ight need, say, a PublicUser role.) I 've cut out m ost real- world at t ribut es t o keep it com pact . Most of t hat st uff, such as t he cust om er's invoice address and order delivery address, isn't part icularly difficult t o handle. I n t he early phases of real applicat ion developm ent , I oft en concent rat e on t he relat ionship bet ween obj ect s ( and on t heir behavior and collaborat ion, of course) rat her t han on specific at t ribut es anyway.
Simple Registry Handling This problem dem ands t he handling of cust om er dat a. This is what I would call a bread and but t er regist ry handling funct ionalit y. I j ust have t o show you m y solut ion t o t his because it reveals som e of m y ideas and t he pot ent ial for including m et adat a for t he business obj ect s.
Figure A- 11 is a snapshot from m y business obj ect definit ion t ool, BODef, showing you t he Customer obj ect . You can see t hat besides t he field nam e and it s t ype, t here's m ore inform at ion at t ached t o a field.
Figu r e A- 1 1 . Th e Customer cla ss in BOD e f [View full size image]
I n Figure A- 12 , which shows a form in t he order applicat ion, t he labels are t aken from t he m et adat a. Wit h t his grid you can add, edit and delet e cust om ers. The code t o achieve t his is rat her com pact ( see t he following code list ing) . The m agic is all in t he generic TableHandler, a class t hat t akes advant age of t he m et a- dat a of t he Customer BO as well as of t he generic façade int erface, IBvDBOReq, and t he BoPlaceholder and DataRow relat ionship of t he archit ect ure.
Figu r e A- 1 2 . A de fa u lt im ple m e n t a t ion of t h e Customer
public CustomerFormList() { InitializeComponent(); _tabHndlr = new TableHandlerGuid(SvcFactory.Facade, CustomerPh.FieldInfoBrowser.BOName); _tabHndlr.TableChanged += new TableHandler.TableHandlerEventHandler (TableHandlerEventHandler); _tabHndlr.HookUpGrid(grd); _tabHndlr.FillGrid(); } TableHandler _tabHndlr; string[] confirmString = new string[] {
"Do you want to add Customer?", "Do you want to save changes to Customer?", "Do you want to delete Customer?" }; void TableHandlerEventHandler(object sender, TableHandler.TableHandlerChangedEventArgs args) { args.AcceptChanges = MessageBox.Show( confirmString[(int)args.TableHandlerChangeType], "Confirm", MessageBoxButtons.YesNo) == DialogResult.Yes; }
There's no point in describing each lit t le det ail. You m ight want t o t ake away ideas of what you can do wit h m et adat a. And not shown here, it 's possible t o have m et adat a describing t he relat ionships bet ween business obj ect s, t oo. ( Associat ion obj ect s are first - class cit izens in m y archit ect ure.)
Order Placement Business Rules You can sort of read bet ween t he lines in Jim m y's problem descript ion and see t hat t his is where he want s us guest writ ers t o dig in. Sure, I 'm gam e. Let m e first t ell you what I choose not t o com m ent on in t he problem descript ion, which isn't t he sam e as not having it im plem ent ed. The search problem is ignored. I n m y priorit y list , it was disqualified due t o lack of space. The sam e goes for t he per cust om er order list ing. The credit abilit y of a new cust om er went t he sam e way. I have classified t he unique num ber of t he order as a m inor challenge in m y fake scenario. I sim ply assign it at t he t im e when an order is subm it t edt he order t aker get s it ret urned at t hat point and doesn't need it before t hen. Good support for t he order t aking/ fill- in process is a given. I pict ured a " builder" [ 4] t hat 's used t o m anipulat e t he order form ( t hink piece of paper) , t he order form being t he m et aphor I 'll reason around. I also im agined t his builder as a serializable obj ect t hat is, upon com plet ion, subm it t ed from t he client t o t he m iddle t ier. [4]
You can, for instance, think of the StringBuilder class that is used to build a string.
The m ult i- user aspect adds a few t ough issues t o solve. For inst ance, when t he order t aker t alks t o t he presum pt ive cust om er and t he price of an it em is changed, should he get t o know t hat im m ediat ely? I t doesn't help t hat t he price is checked wit h t he it em reposit ory because if you ret rieve t he price at 10: 55 a.m . and list it on t he screen, it m ight very well be changed at 10: 56 a.m . The sam e problem exist s for your st ock; is t he it em available or do you need t o order it from your supplier? One way t o solve t his problem is t o have t he m iddle t ier not ify all client s ( order t akers) as soon as t here is a price or st ock change so t hat t he client can t ake m easures for t his. However, t his would com plicat e t hings considerably. The client is no longer only t alking t o t he m iddle t ier, t he m iddle t ier is also, on it s own init iat ive so t o speak, t alking t o all t he ( act ive) client s. And besides t hat , t he t im ing problem isn't elim inat ed, only m inim ized. I want t o keep it sim ple but st ill support t he needs. I need t o look at t he business process. The order t aker t alks wit h a cust om er on t he phone. They discuss what t he cust om er needs, and price and availabilit y is com m unicat ed. Event ually t he cust om er decides t o buy, and t he order t aker subm it s t he order t o t he m iddle t ier. I n response, t he m iddle t ier ret urns a report . The report st at es t he final prices and availabilit y. The lat t er will have an im pact on delivery dat e. All discrepancies wit h t he previously ( orally) given fact s are m arked in t he report ( if any) . The order t aker discusses t hem wit h
t he cust om er and if he is unhappy wit h t hat , t he order can be cancelled. Cancelling t he order is a t ransact ion of it s own, an undo/ delet e t ransact ion. I underst and t hat if you give a price t o a cust om er you m ay have t o st ick t o it t o som e ext ent at least . [ 5] This can be correct ed quit e easily. One cheap way is t o leave t he price set t ing t o a few people who only change prices aft er business hours. Anot her way is t o have t he syst em it self post pone effect uat ion of price changes t o t he next non- dealing m om ent . I f t he client caches price inform at ion, she m ust som ehow have t he knowledge t o refresh ( or drop) t he cache on suit able/ st rat egic occasions. [5]
If you, an order taker, misread two zeros in the price, are you then liable?
The problem wit h availabilit y ( st ock) is, however, in cont rast t o prices, a dynam ic one. Therefore, t he need t o have a report given as a response t o an order subm it t ed st ands. I t m ight as well report any price discrepancies found even if a policy t o avoid t hat is im plem ent ed, j ust t o be sure. The report will also have t he final say on whet her t he order is wit hin t he global m axim um lim it or not and if t he cust om er's credit is exceeded or not . When an order is persist ed, we st ick t o t he price given. We don't want t o t ick our cust om er off wit h an invoice wit h higher prices t han prom ised. This forces us t o st ore t he prom ised prices ( OrderPrice in Figure A- 5) wit h t he order inst ead of j ust referring t o t he it em ( t hat has a price) . An alt ernat ive is t o have price hist ory on t he it em s, which is t oo m uch of a hassle. And, yes, we do not " reopen" subm it t ed orders for com plem ent ing it em s in case of, say, t he cust om er realizing he want s t hree inst ead of t wo of som et hing short ly aft er t alking t o us. We m ight place an order wit h no cost for delivery and/ or invoicing t o t ake care of t his sit uat ion. [ 6] Changing an order line t o fewer it em s is anot her st ory t hat we can handle easily ( it m ight affect high- volum e discount s in t he real world) . [6]
This is one of the many problems in a toy sample like this that are not stated well.
POrderBuilder The support for t aking and handling purchase orders is wit hin t he class POrderBuilder . I t 's a class m eant t o be used bot h in client and m iddle t ier, and it 's t ransport ed via serializat ion over t he wire. POrderBuilder int eract s wit h t he warehouse on several occasions, such as when an order line is creat ed when AddItemToOrder() is called. This int eract ion is capt ured in an abst ract ion called ItemStore consist ing of one single m et hod, GetItem(). The discrepancy report is creat ed by POrderBuilder when calling t he m et hod CheckDiscrepancies(). The t ypical scenario is t hat t he client uses a local version of ItemStore see m ore in t he sect ion ItemStore and different im plem ent at ions in client , server and t est , which gives prices and st ock during t he conversat ion wit h t he cust om er. When t he order is com plet ed, t he order t aker subm it s it t o t he m iddle t ier. At som e point t he m iddle t ier calls CheckDiscrepancies() on t he passed POrderBuilder t o produce t he report . At t his point t he ItemStore is an obj ect living in t he m iddle t ier close t o t he dat abase. To have CheckDiscrepancies() as a m et hod on POrderBuilder is appealing when it com es t o unit t est ing. Take a look at t he next code list ing. A POrderBuilder , bldr, is creat ed at [SetUp], where it is also populat ed wit h a few order lines. The itmStore is a st ub ItemStore also creat ed and associat ed wit h bldr during [SetUp]. The t est sim ulat es, in t he first row, how t he sit uat ion in t he ItemStore has changed regarding st ock when it is t im e t o produce t hat discrepancy report . See t he following code: [Test] public void Stock() { itmStore.Items[0].Stock = 1;
POrderDiscrepancyReport r = bldr.CheckDiscrepancies(); Assert.AreEqual(0, r.PriceCount); Assert.AreEqual(1, r.StockCount); Assert.AreEqual(1, r.GetStockAt(0).NumberOfMissingItems); }
Please pict ure how t he discrepancy report is creat ed early in t he order subm it handling before t he order is com m it t ed. However, t here is a problem concerning t ransact ion handling here, a problem t hat is oft en overlooked or oversim plified. I f you're not careful, t he check can com e out alright , in ot her words wit hout any discrepancies, but when you com m it t he order, for inst ance t he last it em in st ock has been " assigned" t o anot her order and cust om er. I 'm saying t hat t he discrepancy check should happen in t he sam e t ransact ion as t he order com m it and wit h locks t hat prevent ot hers from " st ealing" it em s from t he warehouse t hat you're about t o sell.
ItemStoreDifferent Implementations in Client, Server, and Test I t m ight be int erest ing t o t ake a closer look at ItemStore. I t alked about a local client version as well as a m iddle t ier, t ransact ional, version in t he t ext earlier. What 's t hat all about ? Well, let m e show you. I n Figure A- 13 you can see how POrderBuilder holds a reference t o an ItemStore. The reference is im plem ent ed as a public propert y t hat is assigned by t he applicat ion code. I n t he client I use an im plem ent at ion of ItemStore t hat is, and I 'm sorry if I overst at e t his, very different from t he t ransact ional im plem ent at ion t hat I use in t he m iddle t ier during order subm it . ItemStore isn't m uch of an int erface t o im plem ent when it com es t o t he num ber of m et hods; however, it is very cent ral t o t he POrderBuilder . The ItemStore is, for inst ance, used by all m et hods showed in Figure A- 13 .
Figu r e A- 1 3 . Th e r e la t ion sh ip be t w e e n POrderBuilder a n d ItemStore [View full size image]
I 've im plem ent ed t he client version of ItemStore as a sim ple " read all it em s from st ore and cache." This st rat egy is fine as long as t he num ber of it em s isn't t oo great . I f it com es t o t hat , it 's no big deal t o im plem ent a version t hat queries t he m iddle t ier " sm art er." I n t he client , when t he order t aker has t he privilege t o " work wit h an order," a POrderBuilder is inst ant iat ed and assigned an inst ance of t he client side ItemStore im plem ent at ion. At any t im e, a CheckDiscrepancies() against t he client ItemStore is possible. I f t he cust om er accept s, t he next st ep is subm ission of t he order t o t he m iddle t ier. The POrderBuilder inst ance is serialized over t he wire, but not t he ItemStore inst ance. The reference t o ItemStore is m arked as [NonSerialized]. I n t he façade obj ect , a connect ion/ t ransact ion is creat ed and t he j ob t o t ake t he order is handed over t o t he process obj ect ( t he business obj ect ) . This is shown in here: public POrderDiscrepancyReport TakeOrder(POrderBuilder order)
{ POrder bo = new POrder(); CnTxWrap cn = CnTxWrap.BeginTransaction(bo.CreateConnectionForMe() ); try { POrderDiscrepancyReport res = bo.TakeOrder(cn, order); cn.Commit(); return res; } catch { cn.Rollback(); throw; } }
While we're at it , let 's t ake a look at t he process obj ect 's TakeOrder() m et hod in t he following list ing: [PrincipalPermission(SecurityAction.Demand, Role = Roles.User)] [PrincipalPermission(SecurityAction.Demand, Role = @"InternalUnitTesting")] public POrderDiscrepancyReport TakeOrder(CnTxWrap cn, POrderBuilder order) { ItemStore itmStore = new TxItemStore(cn); order.ItemStore = itmStore; POrderDiscrepancyReport res = order.CheckDiscrepancies(); POrderPh ordr = ((POrderBuilder.IOrderInternal)order.POrder).POrder; new POrder().InsertNoRBS(cn, ordr); OrderLine olproc = new OrderLine(); for(int i=0; i