[Date Prev] | [Thread Prev] | [Thread Next] | [Date Next] -- [Date Index] | [Thread Index] | [List Home]
Subject: Re: [sca-j] Another early morning brainstorm - conversations revisited
Hi Simon, Comments (with drawings this time!) inline. Jim On Aug 11, 2008, at 7:31 AM, Simon Nash wrote:
<snip/>
Again, I believe my approach fits perfectly with Sequence C in a number of scenarios and yours does only if the application makes certain guarantees. I'll break my explanation down into a description of what I think your approach requires and then move to why it is problematic. Specifically, by "your approach" I mean a new programming model that: 1. Requires a forward/request response to be part of the *service* operation 2. Requires that forward request/response operation to return either a genericized CallableReference or the service contract, i.e. public interface OrderService { CallableReference<OrderService> startConversation(); //.... } -- OR -- public interface OrderService { OrderService startConversation(); // .... } 3. Requires the forward request/response service operation to be invoked first by the client 4. Requires that forward request/response pattern to involve executing a service provider implementation instance operation 5. Requires the client make all subsequent invocations for the same conversation to be performed through the proxy returned by startConversation(); 6. Requires that a conversation is never initiated to OrderService using a transacted messaging provider 7. Requires the service provider implementation not to make a callback in startConversation() as well as not make a callback until the client has stored the conversational id (more on the latter below) I believe all of these restrictions are unnecessary, except number 7 in very specific circumstances. Besides the unnecessary restrictions described above, here's a slightly more detailed example of why I believe the approach you are advocating is problematic. In order for Sequence C to be guaranteed, one of two approaches can be taken: Approach 1: The Application Handles It --------------------------------------------------------- Application code must be written in such a way as to guarantee that no callback is issued until the *client* has finished certain operations. Note in many cases this is *not* strictly when the client has received a response from a forward synchronous request/response operation to initiate a conversation. For example, consider the following: A---sync--->B B---async--->C A<---sync---B B<---cb--------C A<---cb----- B A (persist conversation id) One way for application code to guard against this is by placing a restriction that the first B operation not introduce callouts which may result in a callback to A anywhere along the code execution path until A has persisted the conversation id. This may be done by placing a restriction on the service contract semantics that callbacks to A cannot be done until all work has completed for the first B operation and B receives a second operation invocation from A. Waiting for the second operation invocation is important since B may callback before A has had the chance to persist the conversation id. How would your approach solve this differently? If this restriction (number 7 above) is required, then I don't see the necessity for all operations initiating a conversation to be synchronous request/response. Approach 2: Transacted Messaging --------------------------------------------------------- Transacted messaging is used. In order for this to happen, the initial call would need to be non-blocking. If the initial interaction between A and B and persisting the conversation id is performed transactionally, the following sequence can be guaranteed: Trx start A---asyn---> MS (message store) A (persist conversation id) Trx commit MS--------->B B---async--->C B<---cb--------C A<---cb---------------------B (note this invocation would go through a MS but I omitted it for brevity) Both our models will work with Approach 1. However, yours introduces the 7 restrictions I mentioned above, significantly changes the Java SCA model, and will not work with Approach 2. The alternative I am outlining preserves the existing model with the addition of one API (the ability to get the conversation id from the ComponentContext) and works with both approaches. Moreover, it only places the callback restriction on applications when transacted messaging is not used.
Sorry, I don't get what you mean by "SCA would need to override the normal transport-level dispatching semantics"? I have a feeling we are not on the same page with something very fundamental. Guaranteeing steps 5 and 6 happen before 8 can be done in the following cases: 1. A synchronous request/reply initiating a conversation 2. A non-blocking request initiating a conversation 3. A non-blocking request initiating a conversation using transacted messaging In all cases, the proxy generates (or delegates the generation of the conversation id) synchronously prior to the first invocation on a service provider instance. This is transparent to application code. Case 1 ---------------- This case requires the provider implementation not make any invocations that result in a callback to the client between steps a-i. Client Proxy Id Generator Binding Provider Endpoint Host Service Provider Instance | | (a) |---------------> | |(b)-----sync-------->| |(c)<----sync---------| |(d)-------------------------------------------->| |(e)<---------------------------------------------| |------------------------------------>| | | |----------------------------------->(f)| | |<--------------------------------------| |<------------------------------------| (g)|<-------------------------------------------------| |(h)<------------| | (i) |---------------->| | | | | |------------------------------------------------>| | | | |------------------------------------>| | | | |----------------------------------->(j)| | | |<--------------------------------------| | |<------------------------------------| | (k)|<-------------------------------------------------| (l) |<----------------| | One way to express this in code is: Response someAppResponse = forwardService.startConversation(); // steps a-h // get the conversation id, e.g. componentContext.getConversation(forwardReference); // store the conversation id in a database forwardService.proceed(); // step i Case 2 ---------------- In the scenario I outlined where application code does not issue a callback until a certain operation is called (step i): Client Proxy Id Generator Binding Provider Endpoint Host Service Provider Instance | | (a) |---------------> | |(b)-----sync-------->| |(c)<----sync---------| |(e)-------------------------------------------->| |(f)<---------------------------------------------| |(g)<------------| | | |-------------------------------->(h)| | | |-------------------------------------->| (i) |---------------->| | | | | |------------------------------------------------>| | | | |------------------------------------>| | | | |----------------------------------->(j)| | | |<--------------------------------------| | |<------------------------------------| | (k)|<-------------------------------------------------| (l) |<----------------| | The Id Generator may be anywhere in the domain - e.g. local to the proxy or remote, it is implementation-specific - and uses an algorithm that generates unique ids over space and time. Steps a-g are done synchronously from the perspective of the client. Between steps b-c, the SCA domain infrastructure may perform other tasks, such as creating some kind of context, as long synchrony is maintained between a-g. The client performs correlation activity between steps g and i. As long as steps a-g happen before h, and application code does not issue any callback until after j, Sequence C will be guaranteed. The important thing here is is that step h does not need to be synchronous. One way to express this in code is - not it does not change from Case 1 except the first invocation is non-blocking and returns void: forwardService.startConversation(); // steps a-g // get the conversation id, e.g. componentContext.getConversation(forwardReference); // store the conversation id in a database forwardService.proceed(); // step i Case 3 ------------------ A transacted messaging scenario that grouped the two operation invocations would alter Case 2 by not sending the messages until transaction commit: Client Proxy Id Generator Binding Provider Endpoint Host Service Provider Instance (transaction start) | | | | (a) |---------------> | |(b)-----sync-------->| |(c)<----sync---------| |(e)-------------------------------------------->| |(f)<---------------------------------------------| |(g)<------------| | (i) |---------------->| | | | | |------------------------------------------------>| | | (transaction commit) | | | | |-------------------------------->(h)| | | | |-------------------------------------->| | |------------------------------------>| | | | |----------------------------------->(j)| | | |<--------------------------------------| | |<------------------------------------| | (k)|<-------------------------------------------------| (l) |<----------------| | From an application perspective, transacted messaging makes life easy as the former does not need to restrict when callbacks are made. It also provides the added benefit that storing of the correlation id and messaging enqueuing are done transactionally. If this were not the case, the client would be left in an inconsistent state (i.e. enqueuing succeeds but the conversation id save fails). One way to express this in code is: forwardService.startConversation(); // steps a-g // get the conversation id, e.g. componentContext.getConversation(forwardReference); // store the conversation id in a database forwardService.proceed(); // step i The only change from Case 1 is the first invocation is non-blocking and returns void. The only change from Case 2 is both invocations are not sent until the transaction commits. In neither of these three cases is there a change to the transport level semantics. For example, in JMS terms, messages can be sent using auto acknowledge or transacted.
No, I do not believe I am. Application code looks very different in my approach: forwardService.startConversation(); // get the conversation id, e.g. componentContext.getConversation(forwardReference); // store the conversation id in a database forwardService.proceed(); As I outlined in the above three cases, startConversation may be synchronous or one-way. Also, note the "proceed" operation is application-specific and not mandated by what I am proposing. It may be sync or one-way - whatever makes sense for the application. Another example would be: public interface OrderService { @OneWay void orderApples(int qty); @OneWay void orderPlums(int qty); @EndsConversation @OneWay void placeOrder(); }
I completely and fundamentally disagree with this. Transacted messaging is a cornerstone of many enterprise applications. I believe it must be enabled by SCA and should be one of the principal patterns that drives discussion. For example, without a good solution, I will not be able to use SCA (at least without proprietary extensions) on several financial services projects I am working on. I suspect any application that requires reliable messaging in high-throughput environments will be in the same position. Of all the Java-based programming models running around in the wild that claim to be "enterprise-class," I can't think of one that fails to properly support transacted messaging. To the point about transacted messaging making specific assumptions regarding the interaction between client and provider, I would argue this is likely to be the case with many, if not all, transports. Again, I'm biased, but I believe the approach I outlined handles the transacted messaging case as well as the others you highlight with minimal changes and disruption to the specification. It also places less restrictions on application code, uses minimal APIs, bleeds much less SCA into application code, and requires less test infrastructure. In this respect, I believe it is much simpler and less disruptive, while accommodating a wider range of important scenarios. I'd prefer we start with the path requiring less change and restrictions on application code unless there is something fundamentally wrong that cannot be fixed. Jim |
[Date Prev] | [Thread Prev] | [Thread Next] | [Date Next] -- [Date Index] | [Thread Index] | [List Home]