Re: [testdrivendevelopment] "Find or Create" functions: a discussion

"Jeff Bellegarde" <[email protected]> Mon, 25 Nov 2019 09:30:32 +0000
Newsgroups gmane.comp.programming.test-driven-development
Message-ID <CAHfQjYRW3cSjfxBewicKqOtjmsA7oTear_bfha0UOp6J+Z1X-A@mail.gmail.com>
--0000000000005cb5450598286bca
Content-Type: text/plain; charset="UTF-8"

I like to separate between the ideal and pragmatic designs.

If an idealized model says the record should already exist, but for
pragmatic reasons we haven't created it yet, allocating on fetch is
perfectly reasonable.  For example, initializing a friends list only when a
an actual friend is being added sounds fine to me. Treating the Command
part of a Query as a hidden implementation detail is OK.

However, I get more concerned when the value being created is more
complicated. Partially initialized objects are scary.  Worse is if there
might be business rules that prohibit certain values from being created.
Having a get() throw IllegalValue error would be surprising.

I'm fine with the general pattern for trivial values but creating a patient
record as a side effect feels a little off. Your one line example crosses
my threshold of complexity but  I can change the nouns and be fine with it.

-- Jeff Bellegarde

On Sun, Nov 24, 2019 at 2:01 PM Steve Gordon <[email protected]> wrote:

> Pragmatically, in the vast majority of applications much less data is
> required for finding an entry than creating it.  Creating an entry with so
> much missing data can create data integrity problems.
>
> So, given that findOrCreate() should need all the data that Create() would
> need (not just identifying information), I would find it cleaner to just
> allow Create to return the object whether it was created or already existed
> (and signal that the entry already existed if the client code cares).
>
> On Sun, Nov 24, 2019 at 1:55 AM 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 out
>> 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 much
>> 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 we
>> 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 =
>> 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
>> an 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. Some
>> 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::createFromIdentifyingInformation)
>>
>> 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 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 well
>> 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
>>
>> 
>
>

-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.

View/Reply Online (#35759): https://groups.io/g/testdrivendevelopment/message/35759
Mute This Topic: https://groups.io/mt/61872506/3268755
Group Owner: [email protected]
Unsubscribe: https://groups.io/g/testdrivendevelopment/leave/6156322/744556386/xyzzy  [[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-


--0000000000005cb5450598286bca
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>I like to separate between the ideal and pragmatic de=
signs. <br></div><div><br></div><div>If an idealized model says the record =
should already exist, but for pragmatic reasons we haven&#39;t created it y=
et, allocating on fetch is perfectly reasonable.=C2=A0 For example, initial=
izing a friends list only when a an actual friend is being added sounds fin=
e to me. Treating the Command part of a Query as a hidden implementation de=
tail is OK.</div><div><br></div><div>However, I get more concerned when the=
 value being created is more complicated. Partially initialized objects are=
 scary.=C2=A0 Worse is if there might be business rules that prohibit certa=
in values from being created. Having a get() throw IllegalValue error would=
 be surprising.</div><div><br></div><div>I&#39;m fine with the general patt=
ern for trivial values but creating a patient record as a side effect feels=
 a little off. Your one line example crosses my threshold of complexity but=
=
=C2=A0 I can change the nouns and be fine with it.</div><div><br></div><di=
v>-- Jeff Bellegarde<br></div></div><br><div class=3D"gmail_quote"><div dir=
=
=3D"ltr" class=3D"gmail_attr">On Sun, Nov 24, 2019 at 2:01 PM Steve Gordon=
 &lt;<a href=3D"mailto:[email protected]">[email protected]</a>&gt; w=
rote:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0p=
x 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir=
=3D"ltr">Pragmatically, in the vast majority=C2=A0of applications much les=
s data is required for finding an entry than creating it.=C2=A0 Creating an=
 entry with so much missing data can create data integrity problems.<div><b=
r></div><div>So, given that findOrCreate() should need all the data that Cr=
eate() would need (not just identifying information), I would find it clean=
er to just allow Create to return the object whether it was created or alre=
ady existed (and signal that the entry already existed if the client code c=
ares).=C2=A0=C2=A0</div></div><br><div class=3D"gmail_quote"><div dir=3D"lt=
r" class=3D"gmail_attr">On Sun, Nov 24, 2019 at 1:55 AM J. B. Rainsberger &=
lt;<a href=3D"mailto:[email protected]" target=3D"_blank">jbrains762@gma=
il.com</a>&gt; wrote:<br></div><blockquote class=3D"gmail_quote" style=3D"m=
argin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left=
:1ex"><div dir=3D"ltr"><div>Hi, folks. An old issue came back to the surfac=
e this month in consultation with clients and I&#39;d like your opinion. It=
 regards the old &quot;find or create&quot; pattern. It seems to violate Co=
mmand/Query Separation (as I understand it), but it seems handy and harmles=
s, so I&#39;d like to find out more about what you folks think about it. Be=
nign? Problematic?</div><div><br></div><div>I imagine using this with the R=
epository pattern. Let&#39;s say we register a patient in a medical environ=
ment and so we need a UI that reduces as much as possible the number of ste=
ps. We don&#39;t want to force the user to look up a patient just to discov=
er that the hospital has no record of them, so we allow the user to enter s=
ome basic identifying information. This information suffices to either find=
 an existing patient or create a new one if our database doesn&#39;t know t=
hat patient. The result is something like</div><div><br></div><div>Patient =
registeredPatient =3D patientRepository.findOrCreate(patientIdentifyingInfo=
rmation);</div><div><br></div><div>The identifying information might have b=
asics like name, date of birth, it doesn&#39;t matter. We can guarantee tha=
t registeredPatient now represents an Entity in our system, either because =
we found someone that matched the identifying information or because we cre=
ated one.</div><div><br></div><div>This appears to violate CQS, but it seem=
s like a good thing to have. Some individuals struggle with this, because t=
hey don&#39;t know whether this is an area where CQS &quot;doesn&#39;t matt=
er&quot; or an area where CQS is trying to teach them something and they ca=
n&#39;t see what they&#39;re meant to learn. I haven&#39;t thought about th=
is in depth in years, so I feel the same way right now. Drawbacks? Alternat=
ives?</div><div><br></div><div>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</div><di=
v><br></div><div>repository.find(identifyingInformation).orElse(T::createFr=
omIdentifyingInformation)</div><div><br></div><div>where find() returns May=
be TIdentifyingInformation and T has a named constructor for creating a T f=
rom a TIdentifyingInformation. I&#39;m assuming here that TIdentifyingInfor=
mation is enough to provide all the mandatory properties of T.</div><div><b=
r></div><div>With this design, I don&#39;t need a single findOrCreate() fun=
ction any more, because the pieces find() and orElse(T::create) communicate=
 the idea well enough.</div><div><br></div><div>Thoughts? I&#39;m happy to =
see the discussion meander. Is this a totally-solved issue and there&#39;s =
one clear good way to proceed? or is it more a matter of context or prefere=
nce?<br>-- <br><div dir=3D"ltr"><div dir=3D"ltr"><div>J. B. (Joe) Rainsberg=
er :: <a href=3D"https://tdd.training" target=3D"_blank">https://tdd.traini=
ng</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=
"_blank">http://www.thecodewhisperer.com</a></span></div><div><br></div></d=
iv></div></div></div>



<p></p><p></p></blockquote></div>



<p></p><p></p></blockquote></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/35759">View/Reply Online (#35759)</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=3DPri=
vate:%20Re:%20Re%3A%20%5Btestdrivendevelopment%5D%20%22Find%20or%20Create%2=
2%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>


--0000000000005cb5450598286bca--