Hey,
So, yesterday I tried reworking my StreamFlow workflow app into using
the hexagonal architecture. So far I am extremely happy with the
results. One of the things I have had big trouble with before is to
implement the "TellDontAsk" principle. It seemed like no matter what I
did I had to, in the end, ask for model information in various ways,
thus showing all the inner details that I had been trying to encapsulate
with my private mixins etc.
With the hexagonal architecture, where UI can be "at the bottom", and
considered "output", this problem went away. Let me give you an example.
In the app there is a search field and a search result view. In a normal
layered app there would be a UI component that takes the search string
and sends it to the application layer, and then presents the results.
The app layer would have a method like this:
SearchResult search(String query);
This is very problematic though: first of all the search field has to
know about the search result view, so they are coupled. If I then also
want to update some other part in the UI the search field has to know
about this too. Also, it is highly likely that once I get the result, I
have to query the application for other things in order to present the
result.
With hexagonal architecture this mess goes away. Since the flow is only
"in->out" rather than "up-down-up", the application layer method becomes:
void search(String query);
The application layer performs the query. When it is done it then simply
looks up all services that implement SearchObserver, iterates over them,
and pass the result to them. This can be easily done with a SideEffect
of the search method, and gives a good example of when to use
SideEffects. The code is something like this in the SideEffect:
@Service Iterable<SearchObserver> observers;
@This Searcher searcher.
public void search(String query)
{
for(SearchObserver observer: observers)
{
observer.refresh(searcher.searchResult().get());
}
}
---
Since the app layer uses() the UI layer, one of the observers just
happens to be the search result view, which presents the results. If
there had been a status bar it could have also consumed the results and
showed a message like "Found 14 matches". Or more like, a SearchStatus
service would have Observed the search results, which would have
produced the string, which is then in turn sent to StatusObservers, one
of which happens to be the status bar.
If the search takes a long time, the UI would be in trouble with the
first method, as it would essentially freeze when calling search. With
hexagonal architecture the search(string) method can accept the string,
return immediately, and then spawn off an asynchronous search that only
when completed notifies the observers. The time between search and
result can be quite long, but the UI will still be responsive in
between, without the UI having to do the thread trickery. When consuming
the results the UI does, however, have to ensure that it is on the Swing
thread.
In any case, a key point is that the search field *does not have to
know* how to present the results. All it does is take the string and
send it to the application for querying. What happens then is up to the
application and observers of the model that the processing changes.
Input and output are separated in code, but still both are presented on
the UI screen.
In this way there *is only TELL*, no ask. All events come from the
outside, goes to the inside (through app->domain), and then goes out
again. And sometimes the initiator (UI) just happens to be the output too.
This would also simplify testing, as the call to the app and
introspection of the resulting model using a mock observer is quite easy
to do.
NEAT!
/Rickard
_______________________________________________
qi4j-dev mailing list
[email protected]
http://lists.ops4j.org/mailman/listinfo/qi4j-dev