Hi Mike, I’ve responded inline where appropriate.
On 15 Mar 2014, at 23:17, Mike Wilson <[email protected]> wrote: > Thanks for the explanations, Tim, > I use both Mockito and other mocking frameworks on a regular basis so I'm > quite acquainted with the field. Taking inspiration from them for the Async > API was the first thing that crossed my own mind, so I agree totally this is > a good idea. I was happy to see this taken up in Peter's article and the > Async spec. But as always, the devil is in the details ;-) > > Even when having experience with mocking frameworks, using the record first / > invoke later pattern feels unintuitive to me. I'll try to explain why. When > you write your Mockito mock calls, you know that the calls will never trigger > any real invocations (it would be an error if they did). Instead you are > configuring expectations of invocations that another party (your test case) > will perform. The actual invocations of the target methods (imperatively > programmed in the test case code) will happen immediately and without any > record first / invoke later semantics as soon as the test case calls them. > In the OSGi Async scenario the calls on the mediator are supposed to lead to > real invocations and therefore I expect them to trigger without any further > delay, just as they do in my test case. This is an interesting viewpoint. In my view Mockito mocks are frequently about record first / invoke later. In my tests I often create a mock, run my test, and then validate that a certain action has happened, e.g. /* Setup */ MyListener listener = Mockito.mock(MyListener.class); Manager manager = new Manager(); manager.addListener(listener); /* End of setup */ manager.manage(“foo”); /* Test assertions */ Mockito.verify(listener).startedManaging(“foo”); > > Then of course, as you touch on, it must be possible to implement the chosen > model in an orderly way. So here I have two followup questions: > > 1) Did you choose the record first / invoke later design because of > implementation considerations/compromises, or because you feel it is more > natural and intuitive for the user of the API? Obviously there is a balance to be struck here. Primarily an API needs to work well for a given set of common use cases, but it does also need to be implementable in a moderately sane way. Creating a fire-and-forget call against a service is relatively easy with a threadpool and a runnable, so the particular target use case we are optimising for is the one where a client wants to obtain a Promise for the asynchronous work that they’re starting. If you take this as a starting point then I think that the current API feels pretty natural, although I understand that you may disagree. > > 2) What mechanisms do you have in mind that implementations could use for > transferring the recorded calls to Async.call? I'm guessing ThreadLocal is > the main alternative? Obviously ThreadLocal is an option, but the OSGi specifications try to avoid dictating details at this level. I can see a model where an implementation uses the value returned by the mediator to track state, although this does get interesting with the zero-args call method. Is there a particular reason that you’re interested in this detail? Even if the mediator were responsible for immediately starting the asynchronous invocation it would still be necessary for the Async implementation to track the call so that a Promise could be obtained for it later. As I mention above, obtaining a Promise for the invocation is probably the most important use case as far as we’re concerned, although we do need to ensure that the solution addresses the other RFC requirements. Regards, Tim > > Best regards > Mike > > Timothy Ward wrote: > Hi Mike, > > Your interpretation of the behaviour described in the RFC is correct, > including the roles of the mediator object and the async service. I’ll > outline the design process that we went through, and hopefully that will > answer your question. > > One of the core problems associated with making this sort of asynchronous > invocation is that you need to intercept a method call, immediately return a > value that isn’t the real result, eventually provide the result through some > other means. > > It turns out that these requirements are very similar to those of a test > mocking framework, for example Mockito. In Mockito you create a mock (or > mediated) object, and the invocations made upon it are recorded. Configuring > one of these mocks typically involves making the method call you wish to > mock, e.g. > > Mockito.when(mock.doStuff())… > > This pattern works very well for the mediators produced by the Async Service > too. The return type information from the mediator allows the generic type of > the Promise to be determined. Given that the pattern was a natural fit, and > the fact that mocking frameworks are widely used and understood, it was > logical to reuse it. > > In addition to the parallels with mocking, there were several other aspects. > The return value from a method call is rarely a Promise/Future, which means > that there must be some other mechanism to acquire the result. If the > mediator calls directly began the asynchronous work then it would be easy to > accidentally overwrite the Promise representing an earlier execution. The > current API flow allows Async implementations to verify the API usage, and to > throw exceptions if the mediator is used incorrectly. > Mediated objects are not thread safe, and should not be shared between > bundles. The reason that they shouldn’t be shared is because they would allow > other bundles to act using your bundle context. Requiring them to be used > with the Async service encourages safer coding patterns. > > In summary the mediated object isn’t actually a proxy (which is why we chose > the method name mediate, rather than proxy). The mediator simply records the > method calls made upon it, and has nothing to delegate to. The Async > implementation is the service providing the asynchronous behaviour, which is > why it is the one that gets called to begin the execution. > > I hope that makes sense. > > Regards, > > Tim > > On 14 Mar 2014, at 13:28, Mike Wilson <[email protected]> wrote: > >> Async RFC 206 (from March 03) at: >> https://github.com/osgi/design/tree/master/rfcs/rfc0206 >> >> From section 5.4 we learn how to start an asynchronous >> invocation that has a return value: >> >> List asyncList = asyncService.mediate(listRef); >> Promise<Boolean> promise = >> asyncService.call(asyncList.contains("badEntry")); >> >> and one that has no return value (void method): >> >> mediator.clear(); >> Promise<Boolean> promise = asyncService.call(); >> >> The text explains that the actual asynchronous invocation >> doesn't take place until calling Async.call(), ie: >> >> asyncService.call(asyncList.contains("badEntry")); >> ^ send ^ prepare >> async async >> call call >> >> asyncList.clear(); // prepare async call >> asyncService.call(); // send async call >> >> Why did you choose this design, and did not choose to >> let a proxied method send its own async call? >> >> Best regards >> Mike >> >> _______________________________________________ >> OSGi Developer Mail List >> [email protected] >> https://mail.osgi.org/mailman/listinfo/osgi-dev > > _______________________________________________ > OSGi Developer Mail List > [email protected] > https://mail.osgi.org/mailman/listinfo/osgi-dev
_______________________________________________ OSGi Developer Mail List [email protected] https://mail.osgi.org/mailman/listinfo/osgi-dev
