K.U.Leuven, Dept. of Computer Science ... service-oriented programming languages. 1. Introduction ... how Java supports Web Service programming, leading to.
ServiceJ – A Java Extension for Programming Web Services Interactions Sven De Labey, Marko van Dooren, and Eric Steegmans K.U.Leuven, Dept. of Computer Science 200A Celestijnenlaan, B-3000 Leuven, Belgium {svendl,marko,eric}@cs.kuleuven.be Abstract Object-oriented programming languages are losing pace with the rapidly evolving Web Services paradigm. They are unable to deal with the distributed, volatile nature of web services, and they lack expressive language constructs for complex web service interactions. In this paper, we present ServiceJ, a Java extension with integrated support for web service programming. By extending the type system with type qualifiers, ServiceJ realizes important objectives from both paradigms. On one hand, ServiceJ resembles Java by promoting reuse and abstraction, and by supporting typesafe web service invocations. On the other hand, ServiceJ supports late web service binding, Quality of Service negotiation, and transparent web service failover. We provide an in-depth discussion of our type system extension, formally prove its type soundness, and compare our language extension with related object-oriented and service-oriented programming languages.
the implementation of technical middleware algorithms for dealing with low-level volatility and distribution issues [33]. In this paper, we present ServiceJ, an extension of Java with integrated support for Web Service programming. ServiceJ improves other object-oriented solutions in two ways. First, it deals with service volatility by supporting late service binding, QoS constraining, and optimal service selection. Second, it copes with distribution issues by providing transparent service failover and by offering constructs for creating ad hoc web service sessions. This paper is structured as follows. Section 2 assesses how Java supports Web Service programming, leading to a list of design goals for our language extension. Section 3 presents ServiceJ and its new language constructs. Section 4 briefly discusses the formalization of ServiceJ, and Section 5 outlines its implementation. An evaluation of our approach is presented in Section 6. Finally, Section 7 discusses related work, and Section 8 concludes.
2. Java Language Support for Web Services 1. Introduction The paradigm shift from in-house, homogenous distributed systems to diversified cross-organizational web service architectures imposes new challenges on application developers [30]. These challenges originate from (1) the inherent volatility and (2) the distributed nature of web services. Being volatile, service applications must cope with newly joining services, service migration, incompatible service updates and service deprecation. Being distributed, applications must deal with availability problems caused by router failures, network errors or server outages. Object-oriented programming languages such as Java Enterprise Edition 5.0 [22] deliver the main technology for developing enterprise systems, but their current programming models are not adequate for implementing complex interactions with remote web services. By too slowly adopting the web services paradigm, object-oriented programming languages make the programmer responsible for 1 Research funded by a Ph.D. grant of the Institute for the Promotion of Innovation through Science and Technology in Flanders.
From the release of the Java API for XML-based Web Services 2.0 (JAX-WS [23]), the Java Enterprise Edition programming model [22] includes an annotation, @WebServiceRef, to define a web service reference. The information found in this annotation typically consists of (1) the location of the remote WSDL file and (2) the type of the service interface, as shown in the code sample below. The runtime system uses this information to initialize the field with a reference to the remote service endpoint. @WebServiceRef{ wsdlLocation="www.service.url/t.wsdl", type=T.class} T myService; //initialized by container
From the viewpoint of service-oriented computing, this level of support is problematic for a number of reasons: 1. Static Service Binding. The use of wsdlLocation attributes hardwires service references to physical web service locations, thus requiring code revisions to handle service migration and service deprecation. But this is not manageable because the annotations to be updated may be scattered throughout the entire applica-
2.
3.
4.
5.
• WS4. Preference-driven Service Optimisation. A list of candidate services must be sortable according to user-defined quality attributes, thus allowing to select the optimal service for each individual user.
tion. An alternative approach could be to externalize service addresses in a deployment descriptor [22], but even then, the dynamic nature of web services causes (1) frequent and complex revisions of a verbose XML file and (2) application redeployment. Flexibility. By relying on hardwired URLs, Java forces users with different preferences to use the same service, even though there may exist services that better suit their needs. Distribution. The @WebServiceRef annotation makes it impossible to reroute requests to backup endpoints. If a service crashes, the client must either wait until that service recovers, or wait until someone updates the annotation and redeploys the application. Both solutions are unacceptable. Reuse and Adaptation. Exporting the application to a new service architecture forces developers to rewire every @WebServiceRef annotation to the services in that new environment. Program Comprehension. Java makes the programmer responsible for implementing service selection and failover. These algorithms cooperate tightly with the business logic, which obstructs a clean abstraction into reusable libraries or aspects. But their occurrence in the business logic decreases both the readability and the modularity of the source code, which in turn increases maintenance costs as well as the probability to introduce bugs in newer versions of the software.
• WS5. Service Sessions. Complex business operations comprise multiple client-service interactions. The runtime must ensure that such sessions are executed consistently and atomically on the target web service, thus requiring support for ad hoc web service transactions. • WS6. Provider Unawareness. Interactions between a client and its service providers must be done using standard web service protocols (e.g. SOAP), without requiring the provider to install additional software. Our design goals related to object-oriented programming focus on static type-checking, reuse, and abstraction: • OO1. Static Type Checks. The language must support compile-time type checking of web service interactions to increase the odds for encountering problems before the application is put online. • OO2. Reuse. Reusing application components in an environment with different providers must be possible without source code modification. Thus, the business logic must be independent of deployment decisions. • OO3. Abstraction. Regarding issues such as service binding and failover, the language must maximize runtime support and minimize the task of the developer. • OO4. Java Compatibility. Our Java extension must be compilable to Java. This ensures full compatibility with existing Java code and it guarantees that the standard JVM can be reused as an execution environment.
2.1. Goals for Web Service Integration This paper aims at defining an object-oriented language with integrated support for web service interactions. Obviously, such an integrated language must satisfy design goals from both worlds. Therefore, we have defined a number of objectives originating from the world of Web Services, together with some important requirements stemming from traditional Object-Oriented Software Development. The objectives related to web service programming (WS 1–6), on one hand, focus on the provision of expressive language constructs for service selection, and on enhanced runtime support for handling service volatility and distribution:
Java’s annotation-based model for programming web service interactions realizes only OO1, OO4, and WS6.
3. ServiceJ Language Extensions ServiceJ aims at integrating web service interactions with the Java programming model. It does so (1) by introducing type qualifiers to enable transparent failover, (2) by introducing declarative language constructs to fine-tune dynamic service selection, and (3) by providing session blocks to demarcate ad hoc web service transactions.
• WS1. Late Service Binding. Service references must be bound at runtime, possibly taking into account services that were dynamically discovered. • WS2. Service Failover. Being confronted with availability problems caused by server outages, router failures, and network partitioning, applications need support for web service failover to increase the probability to successfully execute a business operation. • WS3. Constraint Handling. Clients must be able to impose functional requirements and Quality of Service constraints on a set of competing service providers.
3.1. Service Pools Advanced language support for enforcing QoS constraints and user preferences (WS 3–4) requires a basic mechanism for late service binding (WS1) and, optionally, for service failover (WS2). Therefore, ServiceJ removes the need to rely on hardwired web service URLs in @WebServiceRef annotations. In stead of directly pointing to these URLs, ServiceJ variables refer to a service pool, a new language concept defined as follows: 2
Definition 1 (Service Pool). A service pool of type T , denoted as PT , is an unordered, unbounded set of interchangeable service endpoints s1 , . . . , sn such that for all 1 ≤ i ≤ n, the type Si of si conforms to T (i.e. Si ≤ T ).
Example. Figure 2 gives an example of the runtime system acting as a mediator to enable late service binding and transparent failover. It shows how a pool variable p of type S is used to invoke operation m (exported by S). At the code level, the pool is referenced as a single virtual service, thus hiding its underlying complexity. The runtime, on the other hand, has full access to the pool contents and injects, for instance, s1 as the active service. After unsuccessfully invoking s1.m, the runtime injects another service, s2, and invokes s2.m, resulting in another error. Finally, after injecting s3, the call s3.m returns successfully and s3 is registered as the active service for further invocations on p.
Figure 1: Java references and ServiceJ pool references There are two main differences between ServiceJ pool variables and normal Java variables. First, both variables differ conceptually, as shown in Figure 1. The Java variable is hardwired to s1 and is unable to contact replicated instances (s2 and s3) if s1 crashes. The ServiceJ pool variable, on the other hand, refers to a pool containing interchangeable service endpoints and enables the runtime system to dynamically bind (and rebind) s to s1, s2, or s3. Second, ServiceJ pool variables differ syntactically from normal variables in that pool variables are declared with an additional type qualifier (pool) [32]. A qualified type such as pool FlightService, for instance, indicates that the runtime system must non-deterministically inject a reference found in the pool of FlightService references. Similar to Java variables, however, a ServiceJ pool variable is bound to one service at the same time (s1 in our example). Thus, programmatically, method invocation on pool variables resembles method invocation in Java. This allows programmers to treat a pool variable as a proxy to a single virtual service. The runtime initializes this virtual service by injecting a pool member into the pool variable. This injected service is called the active service:
Figure 2: Transparent failover in service pools Service Pool = Dynamic Population + Sharing Service pools grow and shrink dynamically as a reaction to service discovery and service deprecation (see Figure 3). Their dynamic nature enables applications to interact with newly discovered services (e.g. Sn) without additional configuration or code modification. ServiceJ does not restrict how references are added to the pool. This can be done, for example, by using an in-house discovery mechanism, or by directly referring to the directory server of a provider.
Property 1 (Single Active Service). A non-empty pool contains exactly one active service. Formally, PT = ∅ ∨ ∃! s ∈ PT such that active(PT ) = s. By transparently binding pool variables (pT ) to services from the corresponding pool (PT ), the runtime enables late service binding. By transparently selecting and injecting a new pool member on service failure, the runtime also enables transparent failover. This self-healing property of pool variables is formalized as follows:
Figure 3: A service pool is a shared set of services that evolves due to service discovery and service deprecation. Another interesting property of service pools is that they are shared between applications, thus admitting central management of a pool registry for an entire enterprise SOA (as shown in Figure 3). Pool sharing improves the annotation-based approach of Java, which scatters service URLs throughout various applications running on the SOA. In this paper, we focus on the language design rather than on deployment issues. As pool management does not
Property 2 (Transparent Failover). Let pT be a pool variable referring to a service pool PT = {s1 , . . . , sn }. Let m be a method exported by T . Define PT,av as the set of reachable services in PT . Then, (1) pT is bound to active(PT ) ∈ PT,av ⊆ PT . (2) PT,av = ∅ ⇐⇒ pT .m succeeds 3
interfere with our design, we refer to [15] for a detailed discussion on pool construction and maintenance.
Example. Figure 5 gives a detailed example of method invocation on constrained pools. It shows how a pool variable p with condition c invokes an operation m. On method invocation, the pool containing S1, S2, S3, and S4 is filtered and only those services that satisfy c are retained (S2 and S3). This explains why S1 is never selected as an active service in Figure 5. After constraining the pool, the call is sent to S2, resulting in an error and causing the failover mechanism to reinvoke m on S3, which handles the request.
3.2. Constraining Service Pools By only requiring that the type of a pool member S conforms to the pool type T (S ≤ T ), basic service pools provide too limited support for imposing membership constraints on pool candidates. Often, programmers need to constrain membership to those services that comply with a set of superimposed functional and non-functional requirements (WS3). Functional constraints, on one hand, relate directly to the business logic of an application. For example, a pool of FlightService instances may be constrained to those travel agents that offer a flight from London to New York. Non-functional requirements or Quality of Service constraints, on the other hand, relate to web service availability, performance, security, etc. Java offers no language support for expressing constraints on web services, thus forcing programmers to hardcode support for iterating through a list of service candidates (WS1) until a service satisfies their requirements (WS3). This lack of abstraction violates our goals related to object-oriented programming (OO1, OO2, and OO3). ServiceJ, on the other hand, makes the runtime system responsible for enforcing membership constraints. At the code level, programmers specify a boolean membership condition with a declarative operation, where. The runtime creates a selective view on the shared pool by selecting only those candidates that satisfy that condition. An example is shown in Figure 4, where two pool variables, f and g, from two different applications each define their own membership conditions c1 and c2 on F, leading to selective, personalized views on a shared service pool (PF ).
Figure 5: The runtime system transparently enforces the constraints that were specified using a where clause.
3.3. Optimal Service Selection Until now, all pool members were assumed to be fully interchangeable as each member satisfies two basic requirements: (1) its type conforms to the pool type, as enforced by the compiler and (2) every member satisfies the membership constraint (if any), as enforced by the runtime system. Therefore, pools are typically used to express service equivalence, thus justifying non-deterministic service selection. Often, however, some services in the pool have more appealing characteristics than others. For example, a pool containing flight booking services from competing travel agents may offer flights at various price levels, and a customer may be interested in booking the cheapest flight available. This breaks our assumption of service equivalence, making the pool qualifier less suitable. Therefore, we define a subtype of the pool qualifier, sequence, to indicate that the runtime system must pay attention to quality attributes during service binding. In other words, the sequence qualifier installs a deterministic service selection algorithm:
Figure 4: Declarative membership conditions c1 and c2 induce a selective view in a shared service pool F.
Definition 2 (Sequence). A sequence of type T, denoted as ST,q is an ordered, unbounded set of interchangeable service endpoints s1 , . . . , sn sorted according to a set of quality attributes q = q1 , . . . , qn such that for all 1 ≤ i ≤ n, the type Si of si conforms to T (i.e. Si ≤ T ).
Property 3 (Constrained Pool Invocation). Let pT be a pool variable referring to a service pool PT with membership condition c. Let m be any method exported by T . Define PT,av,c as the set of services in PT containing only those reachable services that satisfy c. Then, (1) PT,av,c ⊆ PT,c ⊆ PT (2) PT,av,c = ∅ ⇐⇒ active(PT ) ∈ PT,av,c (3) PT,av,c = ∅ ⇐⇒ pT .m succeeds
Developers specify quality attributes using a new declarative operation, orderby, that accepts a primitive Java type or a Java Comparable expression. On method invocation, the runtime system scans through the pool and injects the member that most closely approximates these quality attributes into the sequence variable: 4
Property 4 (Sequence Invocation). Let sT be a variable referring to a sequence ST,q with type T and quality attributes q. Let m be a method exported by T. Define ST,q,av as the subsequence of reachable services in ST,q . Then, (1) ST,q,av ⊆ ST,q (2) ST,q,av = ∅ ⇐⇒ active(ST,q ) ∈ ST,q,av (3) ST,q,av = ∅ ⇐⇒ sT .m succeeds (4) ∃s ∈ ST,q,av such that active(ST,q )