bennadel / Streamlined-Object-Modeling

I am currnently working through the book, Streamlined Object Modeling: Patterns, Rules, and Implementation. True object oriented programming (OOP) is a new world for me and it's really hard for me to wrap my head around. I'd like to get a nice set of code samples here that I can use to practice the modeling techniques outlined in the book.
25 stars 8 forks source link

Responsability for testing Conflict #5

Open bennadel opened 10 years ago

bennadel commented 10 years ago

@jonahx In the book, they talk about whose responsibility it is to check conflicts. With a Group-Member relationship, the book says that it's the Member's responsibility to check to make sure that it doesn't conflict with other Members in the Group. In my Person.addAttraction(Person) method, the check is initiated by the Person:

testAddAttraction( newPerson ) {
    // For each social circle (PSEUDO CODE):
    this.socialCircle[ i ].testAddPersonAttractionConflict( this, newPerson );
}

but the logic for the conflict is actually in the social circle:

https://github.com/bennadel/Streamlined-Object-Modeling/blob/master/examples/01-social-circle/js/model/SocialCircle.js#L155

This is where I get a bit confused. The "love triangle" feels like a business requirement of the social circle, not of the person. As such, the love triangle probably shouldn't be considered a "conflict" between people?

So, is this really a "property test"? When they talk about it in English, it makes sense; but, in the code, it feels much more fuzzy.

Not sure if this question makes sense - sorry :)

jonahx commented 10 years ago

Do you have page number reference in the book?

Because I certainly agree that logic feels like it should be part of SocialCircle. It has no meaning without SocialCircles. In addition, just as in life, people don't get to know all of another person's attractions (the other person will refuse to say). Whereas telling the abstract entity SocialCircle should be no problem. Indeed, you could even enforce this in the Person's getter code, though that's probably overkill.

On Monday, October 21, 2013 4:00:44 PM, Ben Nadel wrote:

@jonahx https://github.com/jonahx In the book, they talk about whose responsibility it is to check conflicts. With a Group-Member relationship, the book says that it's the /Member's/ responsibility to check to make sure that it doesn't conflict with other Members in the Group. In my Person.addAttraction(Person) method, the check is initiated by the Person:

testAddAttraction( newPerson ) { // For each social circle (PSEUDO CODE): this.socialCircle[ i ].testAddPersonAttractionConflict( this, newPerson ); }

but the logic for the conflict is actually in the social circle:

https://github.com/bennadel/Streamlined-Object-Modeling/blob/master/examples/01-social-circle/js/model/SocialCircle.js#L155

This is where I get a bit confused. The "love triangle" feels like a business requirement of the social circle, not of the person. As such, the love triangle probably shouldn't be considered a "conflict" between people?

So, is this really a "property test"? When they talk about it in English, it makes sense; but, in the code, it feels much more fuzzy.

Not sure if this question makes sense - sorry :)

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5.

bennadel commented 10 years ago

@jonahx Unfortunately, I don't have a page number (I've been reading on the Kindle and they use "locations"). See this Screen shot. Basically, the book talks about various constraints and who is responsible for checking which:

I guess where I get hung-up is that the love-triangle validation must be executed when the Person is creating a relationship with another Person, not with a Social Circle. So, the Person, in a real way, has to know that altering a Friendship may impact the rules of the Social Circle. Something about that just seems a little strange.

jonahx commented 10 years ago

Ah okay, I will take a look in a bit. It is a bit more complicated than I thought at first, but still I don't think too bad.

On Monday, October 21, 2013 4:16:20 PM, Ben Nadel wrote:

@jonahx https://github.com/jonahx Unfortunately, I don't have a page number (I've been reading on the Kindle and they use "locations"). See this Screen shot http://screencast.com/t/pS5Uo4K8wX. Basically, the book talks about various constraints and who is responsible for checking which:

  • Type
  • Multiplicity
  • Properties
  • State
  • Conflict

I guess where I get hung-up is that the love-triangle validation must be executed when the Person is creating a relationship with another Person, not with a Social Circle. So, the Person, in a real way, has to know that altering a Friendship may impact the rules of the Social Circle. Something about that just seems a little strange.

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5#issuecomment-26720503.

bennadel commented 10 years ago

@jonahx It's an interesting situation. In the book, one of the examples they give is that with the:

Person - TeamMember

A Person can only be added to a TeamMember (role) if the Person has a valid email address (which would be a "Property" check during TeamMember creation). However, they never address the situation in which a Person's email address is cleared AFTER it is associated with the TeamMember.

Seems like exactly the problem I'm seeing with the love-triangle.

jonahx commented 10 years ago

I think we need to use Observer here:

Person needs a "subscribe()" method that accepts an a target object and method on that object to call back to, like:

person.subscribe(socialCircle, ensureStillValid);

In theory you could subscribe to fine-grained events like "attractionChange", but I think a single subscription to any change event is fine, and anyway you'd want to revalidate for stuff like a DOB change as well. Now inside of person's methods like "addAttraction", you put a call at the end to dispatchEvent, which loops through the subscribers and does something like:

subscribedObject.call(subscribedMethod, self)

which essentially is the equivalent of

socialCircle.ensureStillValid(person);

and now if the person is no longer allowed because of love triangle rules, he gets booted. And the rules themselves remain naturally in SocialCircle

bennadel commented 10 years ago

Hmmm :/ We venture deeper into the territory of the unknown.

From what I read in the book, someone needs to be able to rollback relationships that are partially valid. So, if I had some sort of friendship object (Role), the constructor would have something like:

friend.addPerson( aPerson );
try {
    friend.addSocialCircle( aSocialCircle );
} catch () {
    aPerson.doRemoveFriend( this );  // ---- Rolling back the first half of the relationship.
}

So, it seems that in this case, the dispatched event would have to be cancellable and observable such that it could lead to a rollback.

For example, Sarah and Jon and in the same social circle. Sarah likes Jon, and Jon likes noone. Then, Jon likes Sarah (which is not allowed via the business rules of the Social Circle). This would be kicked off with:

jon.addAttraction( sarah );

... but the SocialCircle would have to override that once it realized that such an attraction could not be formed by two people in the same social circle.

Does that make sense? I guess I'm asking, "who calls the jon.doRemoveAttraction( sarah )"? Is that in the Social Circle? Or it the Jon who announced the event?

Make sense?

jonahx commented 10 years ago

Yes, makes perfect sense. I should have pointed out that the design I suggested above assumes that attractions always just happen and then the necessary changes to SocialCircle needed to accommodate them are silently made (like in life :) ).

The other possibility, which you're suggesting, is that when adding an attraction we check if it's "allowed" first by the SocialCircle.
Neither approach is right or wrong -- it's simply a domain modelling decision. That is, depending on how you want the system to behave, either choice might be correct. As I said, I was just guiding my choice by analogy with the real world, although I can actually think of plenty of real world cases where social rules do end up affecting or changing people's desires, so it's a reasonable domain question to ask.

On Monday, October 21, 2013 9:48:03 PM, Ben Nadel wrote:

Hmmm :/ We venture deeper into the territory of the unknown.

From what I read in the book, someone needs to be able to rollback relationships that are partially valid. So, if I had some sort of friendship object (Role), the constructor would have something like:

friend.addPerson( aPerson ); try { friend.addSocialCircle( aSocialCircle ); } catch () { aPerson.doRemoveFriend( this ); // ---- Rolling back the first half of the relationship. }

So, it seems that in this case, the dispatched event would have to be cancellable and observable such that it could lead to a rollback.

For example, Sarah and Jon and in the same social circle. Sarah likes Jon, and Jon likes noone. Then, Jon likes Sarah (which is not allowed via the business rules of the Social Circle). This would be kicked off with:

jon.addAttraction( sarah );

... but the SocialCircle would have to override that once it realized that such an attraction could not be formed by two people in the same social circle.

Does that make sense? I guess I'm asking, "who calls the jon.doRemoveAttraction( sarah )"? Is that in the Social Circle? Or it the Jon who announced the event?

Make sense?

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5#issuecomment-26749788.

bennadel commented 10 years ago

@jonahx I am sure this sounds like a real jr mindset, but there's something about event-driven programming that doesn't sit right with me when the workflow is mission critical. On the client-side, with JavaScript, I like when things are event-driven because it makes things feel very disconnected and decoupled and that they can change independently. But, that's nice because the things I'm dealing with can change independently.

For example, if I have a list of Widgets that listen for a "widgetCreated" event and they update the list automatically, that's cool. But, if it fails, I guess I don't worry too much (maybe being able to refresh a page has made me too relaxed about certain kinds of bugs).

But, if action Y must happen after action X, event-driven communication always feels like a layer of indirection meant to decouple things that were very coupled.

Or, maybe it's just such a new concept that it scares me :)

jonahx commented 10 years ago

Yeah it sounds like just a misunderstanding. I'm not entirely clear on what your apprehension is though -- can you clarify?

All we are doing is creating a new service on the person object (subscribe), and then guaranteeing that we tell subscribers about any changes that occur in the person object. Calling it "event" driven is just a metaphor. It's totally unrelated to anything like a native browser "click" event. We simply have our custom domain objects, and we're giving them subscription and notification behaviors so that they can collaborate without coupling themselves to one another. Does that make sense?

On 10/22/2013 3:33 AM, Ben Nadel wrote:

@jonahx https://github.com/jonahx I am sure this sounds like a real jr mindset, but there's something about event-driven programming that doesn't sit right with me /when/ the workflow is mission critical. On the client-side, with JavaScript, I like when things are event-driven because it makes things feel very disconnected and decoupled and that they can change independently. But, that's nice because the things I'm dealing with /can/ change independently.

For example, if I have a list of Widgets that listen for a "widgetCreated" event and they update the list automatically, that's cool. But, if it fails, I guess I don't worry too much (maybe being able to refresh a page has made me too relaxed about certain kinds of bugs).

But, if action Y must happen after action X, event-driven communication always feels like a layer of indirection meant to decouple things that were /very/ coupled.

Or, maybe it's just such a new concept that it scares me :)

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5#issuecomment-26771259.

bennadel commented 10 years ago

@jonahx I suppose it's just the unfamiliarity of pub-sub on the server-side that feels very odd to me. There's something about pub-sub that feels like it lacks holistic orchestration. So, going back to the Attraction, imagine that a person announced an "attractionCreated" event. Now, imagine that there are two other objects that listen for this: the SocialCircle, which can "override" the event; and, a notification object which sends a "Someone likes you!" email.

The order of those actions is determined by the order in which the event subscriptions were made. So, if the "notification" object subscribed prior to the social circle, then you could have the system send out an email even if the social circle was about to override said event. This would mean, essentially, that the notification was sent out inappropriately.

This is the kind of non-tangible, non-specific fear that I have when I think about pub-sub for critical workflows. It's like you have these objects that we try to keep decoupled; but, we actually just end up coupling them in different ways (such as making sure that event bindings are made in the appropriate order).

Again, this is all theory, not practice :)

jonahx commented 10 years ago

It should be practice!

You bring up legitimate concerns, but I fear you're throwing out the baby with the bathwater. There are solutions to the points you brought up. Also, the phrase " it lacks holistic orchestration" struck me as dangerously close to "it lacks a top down control structure," a lack which is of course the point of OO. Remember the West book: the ideal is a bunch of little independent entities cooperating without knowing about each other and producing emergent behavior -- think ants, termites.

Now, imagine that there are two other objects that listen for this: the SocialCircle, which can "override" the event; and, a notification object which sends a "Someone likes you!" email. The order of those actions is determined by the order in which the event subscriptions were made. So, if the "notification" object subscribed prior to the social circle, then you could have the system send out an email even if the social circle was about to override said event. This would mean, essentially, that the notification was sent out inappropriately.

This is change in business requirements, but it's still a valid question. There are a bunch of ways you could go here. If you recall the the event dispatcher in the West book had a priority associated with each event. You could make the check with SocialCircle higher priority, and then have a rule where you don't notify subsequent subscribers if an error is thrown. I'm not crazy about that though.

Another possibility: SocialCircle could listen to the "TestAdd" event, and Email could listen to the "SuccessfulAddAttraction" event. That's reasonable and gives maximum decoupling.

But there's another possibility you might like better. There's a valid argument that decoupling SocialCircle and Person isn't even desirable -- they are coupled conceptually. On the other hand Email should not be coupled with person. So a hybrid approach could work here. Person explicity sends messages to its SocialCircles during testAddAttraction. But email still uses the pub/sub approach, and the event it listens for is "SuccessfulAddAttraction", which only gets fired during doAddAttraction.

bennadel commented 10 years ago

@jonahx Yes, I do like me a top-down control structure :) That's definitely the last decade of web development peeking through. Sometimes it's very hard to think about things as decoupled when, from a business perspective, they are highly coupled.

I like the idea about the mixed-model approach - coupling internally and then event-driven for the notification.

On that note, the mental model I've sort of been formulating in my head is that the Domain Model is all about "state" and making sure state transitions are all valid. That said, it seems that things not involved in state wouldn't go directly in the domain model. The example I think of is the Email. If the system required an email to go out any time an "Attraction" was created, that feels like a "consequence" of state transition and not a part of the state itself. Meaning, the sending of the email is not tracked in the business model; and, as such, none of the entities should initiate the email directly.

Meaning, Person doesn't have a "this.sendAttractionEmail()" method as this has no bearing on state. Instead, something external to the Person would listen for the "add" event you mentioned and send appropriately.

Is that crazy? Or is that legit?

jonahx commented 10 years ago

Hmmm... honestly i'm not sure if that mental model is going to lead to OO decisions or not. I think I'd need to hear you talk through a few more examples to know.

However, there will certainly be other domain objects that you should not be instantiating but should be communicating with through events.
That is, it's not only the "outside the system" nature of email that makes it a good candidate for pub/sub. The reason I think it's probably (or at least arguably) okay to give person a direct handle to SocialCircle is that I think they will change together a lot of the time. But that won't be true with most other domain objects.

For example, let's say we want to introduce MatchMakers into the system. Special people that know about who looks whom and setup 1 time visits where a normally disallowed person is allowed to visit a SocialCircle meeting to see if they can turn a 1 way attraction into a 2 way attraction. A MatchMaker would definitely want to listen to those AddAttraction events, but I would not want Person directly coupled to MatchMaker -- as the concepts are independent.

On Thursday, October 24, 2013 2:54:07 PM, Ben Nadel wrote:

@jonahx https://github.com/jonahx Yes, I do like me a top-down control structure :) That's definitely the last decade of web development peeking through. Sometimes it's very hard to think about things as decoupled when, from a business perspective, they are highly coupled.

I like the idea about the mixed-model approach - coupling internally and then event-driven for the notification.

On that note, the mental model I've sort of been formulating in my head is that the Domain Model is all about "state" and making sure state transitions are all valid. That said, it seems that things not involved in state wouldn't go directly in the domain model. The example I think of is the Email. If the system required an email to go out any time an "Attraction" was created, that feels like a "consequence" of state transition and not a part of the state itself. Meaning, the sending of the email is not tracked in the business model; and, as such, none of the entities should initiate the email directly.

Meaning, Person doesn't have a "this.sendAttractionEmail()" method as this has no bearing on state. Instead, something external to the Person would listen for the "add" event you mentioned and send appropriately.

Is that crazy? Or is that legit?

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5#issuecomment-26989362.

bennadel commented 10 years ago

@jonahx Since this is so new to me, I don't have a great way to codify my feelings just yet. If I had to maybe put some rules to it, maybe I'm feeling like a business entity shouldn't directly invoke actions (other than by announcing events) that it cannot confirm the completion of. For example:

These all feel like things the business entity cannot confirm because they are not reflected in the business entities internal state (or the state of its collaborators).

I don't know, perhaps I'm getting ahead of myself.

bennadel commented 10 years ago

@jonahx FYI, I didn't forget about all this stuff. But, the "visual" aspect of it was lack-lust and I needed to change context for a minute and make it more fun. So, I'm porting my "TinyTest" framework over to an AngularJS/RequireJS context:

https://github.com/bennadel/TinyTestJS

Anyway, once that is done (soon), I'll add it to this stuff so I can see some more "tests" run :D

Just needed a mental refresh.

bennadel commented 10 years ago

@jonahx FYI, I pushed the first release of TinyTestJS. Now I'm gonna try to integrate it with this project :D Woot woot!

jonahx commented 10 years ago

Nice! I'll keep my eye on it.

On Thursday, October 31, 2013 4:07:45 PM, Ben Nadel wrote:

@jonahx https://github.com/jonahx FYI, I pushed the first release of [TinyTestJS][https://github.com/bennadel/TinyTestJS]. Now I'm gonna try to integrate it with this project :D Woot woot!

— Reply to this email directly or view it on GitHub https://github.com/bennadel/Streamlined-Object-Modeling/issues/5#issuecomment-27488242.

bennadel commented 10 years ago

Woot, finally integrated my Tiny Test JS into the project:

http://bennadel.github.io/Streamlined-Object-Modeling/examples/01-social-circle/tests/index.htm

jonahx commented 10 years ago

:+1: