Closed jdickey closed 8 years ago
To restate what likely "ought" to be obvious from the four-step description in the opening comment:
There are four conversations with either three or perhaps four "port"-level Wisper listeners that this service engages in; other than those, it is a black box with no caller-accessible attributes. These ports are as follows:
User
entity based on the parameters specified to #call
; and, finallyHow these "listeners" or remote ends of the various message conversations accomplish their tasks is beyond the scope of this class. All we're concerned with here is the domain logic of how a new-user registration request is processed and the interactions it participates in with outside services. Otherwise, we'll never finish.
One final comment for the night: we're mulling over the idea of evolving our use-case development process, currently in its second use, to require some form of documentation for each use case. This would describe how it was used; what interactions (Wisper broadcast-message conversations and others as appropriate) are engaged in; and what data is made accessible to code outside the service object after completion. YAGNI and "Use the tests, Luke" are arguing against that in its entirety; concern over ability to recall or rediscover required information efficiently when use case objects are tied into an application argues for some form of it; and we're standing in the middle wondering if the risk truly justifies the time spent, or even time spent thinking through such a process.
should it be documentation at a Use Case level then or at a service object level? implementation of a later use case would in some cases overwrite or expand upon the data/messages exposed previously right?
use case == service in this discussion; a use case is a specific type/context of service.
I think I’ll solve, or at least punt, the problem by having the complete golden path be the first thing in the test spec for the use-case class, instead of buried idiosyncratically farther down. I've also been reading a good book by Alistair Cockburn on use cases, and it makes a few useful points (buried in among the 2000-era last gasps of the UML-oriented and CASE-oriented sideshows).
Commit 22fbca6 reworked the test specs for SummariseContent
in what is intended, anyway, to be a more descriptive, documentary style:
#call
after correct setup;#call
without having done the necessary setup.Or, in more general terms:
These failure modes might result from improper external setup (e.g., not setting up a correct Wisper listener) or detection of an internal error within the API-method black box. What happens in SummariseContent#call
or, rather its persistence interface, fails to retrieve article data because the database server crashes? We don't have anything now that explicitly speaks to that. Is that something we should worry about right now? In the rush to ship 0.5, arguably not; more rigorous functional tests for the delivery-system implementation may well suffice.
And now we remind ourselves of some of the towering warts on the newest way of doing things.
Repeat after me:
Use cases (and the form objects they depend on) are stateful, even though the value objects they take as input and output aren't.
Forgetting that bit of hard-earned wisdom is an excellent way to fritter away half of what passes for a "working day" here. A five-minute review of an earlier demo project, model_form_demo
, turns up this form object which is used by this controller action responder class. (Controller action responders were our home-baked version of what the industry calls form objects.)
Note how the form object has mutable internal state, since it includes Virtus.model
rather than Virtus.value_object
, as well as including ActiveModel::Validations
, (which adds an errors
instance variable to the class, among other things). We can (and may well, use different validation classes/logic, but the template still holds..
In the responder/use case, an instance of the form object (which wraps an entity-like object, remember) is created and then validated using the form object's defined validations. Our use-case usage will be very similar.
One difference between our new use-case usage of form objects and what we'd done previously is that form objects are expected to be unique to each use case. When an application only used a handful, adding their classes to a top-level namespace such as FormObjects
was a reasonable organisation strategy; given that the new prolog-use_cases
Gem essentially packages each use case, and thus each client of a particular form object, separately, that style no longer seems appropriate. Rather than, say, FormObjects::RegisterNewMember
, this use case's form-object class would be Prolog::UseCases::RegisterNewMember::FormObject
. In the presently-viewed-as-unlikely event that a single use case would employ multiple form objects, names can be selected to suit.
Onward!
Using form objects (rather than a "raw" Virtus value object) would also move the Wisper conversations from the use case to the form object, further apparently simplifying the implementation code (if not the tests, which need to talk to Wisper as before). Yes, we're just reshuffling deck chairs, but we're not asking the entire football side to sit in one chair.
And, in theory, given sufficient quantities of that mythical resource known as time, we could DRY that test setup up nicely. In theory, there's no difference here between theory and practice; in practice, a calendar is a thing.
Well, dry-validation
had been on our "check this out when you find yourself reaching for the equivalent" list and, after an hour and a half of poking, the "equivalent" that we'd previously used for form objects, ActiveModel::Validations
, is a lot less work (even at the cost of dragging tonnes more code into the Gem dependency list). :disappointed:
Right. So we want to add a form object to our use-case class, to handle the data validation and mastication. The problem is that, to do that presently, Wisper listeners are being attached to the use case object, to provide services such as current user name, repository search, and UI notifications. The Wisper conversations, in the current use case, are associated with the validation (and eventually with saving the created User
entity). Objects enclosed within the use case must not be Publishers in their own right, but must be delegated the #broadcast
(alias #publish
) method from the containing class (or its containing class, etc.) We are clearly and drastically exceeding Wisper's own target usage scenario. This is rapidly becoming unwieldy to and beyond the point of farce.
We have three alternatives presently visible:
Prolog::Core
, passing objects into use-case initialisers that respond to messages corresponding to our existing/envisioned Wisper usage, with these in turn being passed into any child classes (such as the previously-mentioned current-user checker). There's a voice in the back of our heads screaming No, don't do that, but the long explanation of why is mumbled to the point of unintelligibility. Anybody who sees any problems with this, now is the time; we're not talking about core entities, after all.Thoughts?
both 1 and 2 are inelegant at best, door #3
Per our discussion on Slack last night, proceeding with Option 2. It's going to involve more total work doing that and then ripping it out in favour of an Option-3-like solution after 0.5 ships, but I believe it adds less risk for additional delay to 0.5 itself. We're already in dangerous waters here.
As presented in Feature Strategy and Planning (Issue #1), the second use case, from the user perspective, is "Sign up as a new member". Following the pattern established by
Prolog::UseCases::SummariseContent
, this new use-case implementation, tentatively namedProlog::UseCases::RegisterNewMember
, will implement the domain logic required to accomplish that task.Continuing with our spin on hexagonal architecture, this will involve Wisper conversations with external subsystems ("ports", in hexagonal terminology) that accomplish the following tasks in response to a call to
RegisterNewMember#call
, supplying the proposed new-member details as parameters:SummariseContent
;Prolog::Core::User
entities similar to theArticle
persistence listener developed forSummariseContent
;User
entity;SummariseContent
provides a facility to report errors, provides an additional capability to report successful registration.If this sounds more complicated than
SummariseContent
, that's because, at a feature level it is. We expect, however, to be able to proceed relatively more quickly as many of the basic process questions involved in spinning up a use-case class and then updating the Gem have now been answered.