Re: [testdrivendevelopment] "Find or Create" functions: a discussion
"Nat Pryce" <[email protected]> Sun, 24 Nov 2019 11:32:31 +0000
| Newsgroups | gmane.comp.programming.test-driven-development |
|---|---|
| Message-ID | <CADGzuY8e9jdNUALz6TuBFyXnpk0E=3QLEWX+wpEjEUpYCOA0PQ@mail.gmail.com> |
--0000000000004ebf130598160146 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Apologies for brevity. I=E2=80=99m on my phone. I think it depends on how transactions are being managed. If called within a transactional scope, I=E2=80=99d be happy with separate =E2=80=9CfindPat= ient=E2=80=9D that returns an optional patient, and a =E2=80=9CcreatePatient=E2=80=9D that ta= kes the minimal properties needed to record a new patient in the system. That would allow the properties used to find a patient and the properties used to create a new patient to vary as the application requires. But if transactions & concurrency control are being managed behind the API= , they may _have_ to be combined into a single operation. On Sun, 24 Nov 2019 at 08:55, J. B. Rainsberger <[email protected]> wrote: > Hi, folks. An old issue came back to the surface this month in > consultation with clients and I'd like your opinion. It regards the old > "find or create" pattern. It seems to violate Command/Query Separation (= as > I understand it), but it seems handy and harmless, so I'd like to find o= ut > more about what you folks think about it. Benign? Problematic? > > I imagine using this with the Repository pattern. Let's say we register = a > patient in a medical environment and so we need a UI that reduces as muc= h > as possible the number of steps. We don't want to force the user to look= up > a patient just to discover that the hospital has no record of them, so w= e > allow the user to enter some basic identifying information. This > information suffices to either find an existing patient or create a new = one > if our database doesn't know that patient. The result is something like > > Patient registeredPatient =3D > patientRepository.findOrCreate(patientIdentifyingInformation); > > The identifying information might have basics like name, date of birth, = it > doesn't matter. We can guarantee that registeredPatient now represents a= n > Entity in our system, either because we found someone that matched the > identifying information or because we created one. > > This appears to violate CQS, but it seems like a good thing to have. Som= e > individuals struggle with this, because they don't know whether this is = an > area where CQS "doesn't matter" or an area where CQS is trying to teach > them something and they can't see what they're meant to learn. I haven't > thought about this in depth in years, so I feel the same way right now. > Drawbacks? Alternatives? > > I was also thinking about how to design this, and it seems to me like a > special case of getOrAbsent(), so that I could implement the generic > findOrCreate() algorithm with something like > > > repository.find(identifyingInformation).orElse(T::createFromIdentifyingI= nformation) > > where find() returns Maybe TIdentifyingInformation and T has a named > constructor for creating a T from a TIdentifyingInformation. I'm assumin= g > here that TIdentifyingInformation is enough to provide all the mandatory > properties of T. > > With this design, I don't need a single findOrCreate() function any more= , > because the pieces find() and orElse(T::create) communicate the idea wel= l > enough. > > Thoughts? I'm happy to see the discussion meander. Is this a > totally-solved issue and there's one clear good way to proceed? or is it > more a matter of context or preference? > -- > J. B. (Joe) Rainsberger :: https://tdd.training :: http://www.jbrains.ca > :: http://www.thecodewhisperer.com > >=20 > > -- http://www.natpryce.com -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#35743): https://groups.io/g/testdrivendevelopment/mess= age/35743 Mute This Topic: https://groups.io/mt/61872506/3268755 Group Owner: [email protected] Unsubscribe: https://groups.io/g/testdrivendevelopment/leave/6156322/74455= 6386/xyzzy [[email protected]] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- --0000000000004ebf130598160146 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div><div dir=3D"auto">Apologies for brevity. I=E2=80=99m on my phone.</div= ><div dir=3D"auto"><br></div><div dir=3D"auto">I think it depends on how tr= ansactions are being managed. If called within a transactional scope, I=E2= =80=99d be happy with separate =E2=80=9CfindPatient=E2=80=9D that returns = an optional patient, and a =E2=80=9CcreatePatient=E2=80=9D that takes the m= inimal properties needed to record a new patient in the system.=C2=A0 That = would allow the properties used to find a patient and the properties used t= o create a new patient to vary as the application =C2=A0requires.</div></di= v><div dir=3D"auto"><br></div><div dir=3D"auto">But if transactions & c= oncurrency control are being managed behind the API, they may _have_ to be = combined into a single operation.=C2=A0</div><div dir=3D"auto"><br></div><d= iv dir=3D"auto"></div><div dir=3D"auto"><br></div><div dir=3D"auto">On Sun,= 24 Nov 2019 at 08:55, J. B. Rainsberger <<a href=3D"mailto:jbrains762@g= mail.com">[email protected]</a>> wrote:<br></div><div><div class=3D"g= mail_quote"><blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;bo= rder-left:1px #ccc solid;padding-left:1ex"><div dir=3D"ltr"><div>Hi, folks.= An old issue came back to the surface this month in consultation with clie= nts and I'd like your opinion. It regards the old "find or create&= quot; pattern. It seems to violate Command/Query Separation (as I understan= d it), but it seems handy and harmless, so I'd like to find out more ab= out what you folks think about it. Benign? Problematic?</div><div><br></div= ><div>I imagine using this with the Repository pattern. Let's say we re= gister a patient in a medical environment and so we need a UI that reduces = as much as possible the number of steps. We don't want to force the use= r to look up a patient just to discover that the hospital has no record of = them, so we allow the user to enter some basic identifying information. Thi= s information suffices to either find an existing patient or create a new o= ne if our database doesn't know that patient. The result is something l= ike</div><div><br></div><div>Patient registeredPatient =3D patientRepositor= y.findOrCreate(patientIdentifyingInformation);</div><div><br></div><div>The= identifying information might have basics like name, date of birth, it doe= sn't matter. We can guarantee that registeredPatient now represents an = Entity in our system, either because we found someone that matched the iden= tifying information or because we created one.</div><div><br></div><div>Thi= s appears to violate CQS, but it seems like a good thing to have. Some indi= viduals struggle with this, because they don't know whether this is an = area where CQS "doesn't matter" or an area where CQS is tryin= g to teach them something and they can't see what they're meant to = learn. I haven't thought about this in depth in years, so I feel the sa= me way right now. Drawbacks? Alternatives?</div><div><br></div><div>I was a= lso thinking about how to design this, and it seems to me like a special ca= se of getOrAbsent(), so that I could implement the generic findOrCreate() a= lgorithm with something like</div><div><br></div><div>repository.find(ident= ifyingInformation).orElse(T::createFromIdentifyingInformation)</div><div><b= r></div><div>where find() returns Maybe TIdentifyingInformation and T has a= named constructor for creating a T from a TIdentifyingInformation. I'm= assuming here that TIdentifyingInformation is enough to provide all the ma= ndatory properties of T.</div><div><br></div><div>With this design, I don&#= 39;t need a single findOrCreate() function any more, because the pieces fin= d() and orElse(T::create) communicate the idea well enough.</div><div><br><= /div><div>Thoughts? I'm happy to see the discussion meander. Is this a = totally-solved issue and there's one clear good way to proceed? or is i= t more a matter of context or preference?<br>-- <br><div dir=3D"ltr" data-s= martmail=3D"gmail_signature"><div dir=3D"ltr"><div>J. B. (Joe) Rainsberger = :: <a href=3D"https://tdd.training" target=3D"_blank">https://tdd.training<= /a> :: <span><a href=3D"http://www.jbrains.ca" target=3D"_blank">http://www= .jbrains.ca</a> :: <a href=3D"http://www.thecodewhisperer.com" target=3D"_b= lank">http://www.thecodewhisperer.com</a></span></div><div><br></div></div>= </div></div></div> <p></p><p></p></blockquote></div></div>-- <br><div dir=3D"ltr" class=3D"gm= ail_signature" data-smartmail=3D"gmail_signature"><a href=3D"http://www.nat= pryce.com" target=3D"_blank">http://www.natpryce.com</a></div> <div width=3D"1" style=3D"color:white;clear:both">_._,_._,_</div> <hr> Groups.io Links:<p> You receive all messages sent to this group. <p> <a target=3D"_blank" href=3D"https://groups.io/g/testdrivendevelopment/mes= sage/35743">View/Reply Online (#35743)</a> | <a target=3D"_blank" href=3D"mailto:[email protected]?subj= ect=3DRe:%20Re%3A%20%5Btestdrivendevelopment%5D%20%22Find%20or%20Create%22%= 20functions%3A%20a%20discussion">Reply To Group</a> | <a target=3D"_blank" href=3D"mailto:[email protected]?subject=3DPr= ivate:%20Re:%20Re%3A%20%5Btestdrivendevelopment%5D%20%22Find%20or%20Create%= 22%20functions%3A%20a%20discussion">Reply To Sender</a> | <a target=3D"_blank" href=3D"https://groups.io/mt/61872506/3268755">Mu= te This Topic</a> | <a href=3D"https://groups.io/g/testdrivendevelopment/post">New Topic</a>= <br> <br> <a href=3D"https://groups.io/g/testdrivendevelopment/editsub/3268755">Your= Subscription</a> | <a href=3D"mailto:[email protected]">Contact Group Own= er</a> | <a href=3D"https://groups.io/g/testdrivendevelopment/leave/6156322/7445563= 86/xyzzy">Unsubscribe</a> [[email protected]]<br> <div width=3D"1" style=3D"color:white;clear:both">_._,_._,_</div> --0000000000004ebf130598160146--