Service Routines
Service, Model, Routine, Signature, and Context
The first step in service-oriented programing (SOP) is to identify all the services the end user needs to use and how they relate to each other in a compound service request, an exercise often known as service modeling. Once services have been identified, corresponding service providers define the kind of data they contain - service contexts - and any subroutines that can process the data. Each distinct subroutine is known as a service operation defined by the provider’s service type used as a reference to service providers. Service providers communicate with well-defined declarative service requests called service models or imperative service requests called service exertions. Compound requests are called service mograms that are aggregations of both models and exertions expressed in a relevant service-oriented languages. In SORCER everything ia a service means that service providers and service requestors (requests) are services. Therefore exertions and models are services ( see top-level interfaces and classes).
Everything in SORCER sits on top of a service mograms. An exertion - imperative service - is supported with three basic concepts: signature, context, and service (see terms in Conceptualization of Service Orientation tutorial. Exertion-oriented Language (EOL) incorporating these concepts is a domain specific language (DSL). It is a metamodel in the form of collection of concepts that are the vocabulary with which you are talking about creating and requesting services. It provides a simple functional syntax with a collection of relevant operators implemented in Java. EOL expressions can be executed as Java source code or as textual scripts. In the form of scripts, EOL service expressions are called service netlets or shortly netlets and interpreted with the SORCER network shell - nsh.
Usually every SORCER service is made up of one Gradle project. What a project represents depends on what it is that you are doing with a service. For example, a project might represent a single service provider only. It might represent a distribution assembled from the classes of other related providers or service beans along with the provider service UI and requestors. A project does not necessarily represent a service provider and requestor to be built. It might complement with things to be done, such as deploying your service to staging or production environments or running services with relevant Gradle startme/stopme tasks.
Each service project builds one or more artifacts. These artifacts can include a provider jar, its downloadable code (dl) jar, its service UI jar, and requestor jar.
A service is the work performed in which a service provider (one that serves) exerts acquired abilities to execute a computation. The work performed is requested by a service requestor. The SORCER environment is a service-to-service (S2S) system where everything is a service. Don’t worry if this seems a little vague for now. The provided examples step-by-step will clarify the service-oriented concepts as illustrated in the UML diagram of SORCER Top Level Types.
As it is illustrated in the above UML top-level diagram providers, signatures, service contexts, var/par-models, and exertions all are services.
The basic SML operators define service-oriented entities of a service model as follows:
Signature: a classifier of provider actions `sig([<name>], <selector>, <service type>, [<provider name>])` sig("add", AdderImpl.class) sig("add", Adder.class, prvName("Mike Adder"))
Two signatures are defined with the sig operator. The first one, a remote signature, is defined with an operation add of the service type Adder (Java interface) and a provider name Mike Adder. It defines a remote provider that implements the Adder interface and is named Mike Adder. A provider name is optional. When omitted then the signature refers to any provider in the network that implements the signature’s service type. The second signature defines a local provider as an instance of class AdderImpl. It is assumed that it implements the Adder interface type. Both signatures do not specify an exact instance of provider - a signature is bound to a provider at runtime by the SORCER Operating System. Both bind and create roles of signatures for local/remote operations are illustrated in the ServiceType" UML class diagram.
Service Provider `provider<signture>)` provider(sig("add", Adder.class, prvName("Mike Adder"))
The provider operator, can be used for testing provider accessibility, returns a provider bound to this signature.
Elementary Service: an action of a single provider (elementary exertion) service("t5", sig("add", Adder.class), context("add", inEnt("arg/x1", 20.0), inEnt("arg/x2", 80.0), result("result/y")))
The service operator returns an exertion that is of the Service type as well. There are many types of exertions (local, remote, tasks, batches, jobs, blocks) you do not need to now when which class is used. Just use the service operator with proper argument types explained below and use the Service or Exertion reference to the created service model. Most arguments for the service operator are usually not ordered. Frequently used operators are: sig, context, service, strategy, and pipe. The last three only for compound exertions. A String argument of the service operator is just a name of the exertion. A task and batch exertion do not have component exertions, each job exertio has component exertions with data context (created with the context operator). A task with multiple signatures is called a batch exertion. For short we call typed exertions as: task, job, block, and batch with the corresponding typed aperators to the generic ‘service’ operator. Each exertions can be local or remote and compound exertions can combine local and/or remote component exertions.
Review the simple services in examples/sml/src/test/java/sorcer/sml/services.
Service Context: a collection of path:value associations (entries) `context( { <entry> })` Context<Double> cxt = context(ent("arg/x1", 1.1), ent("arg/x2", 1.2), ent("arg/x3", 1.3), ent("arg/x4", 1.4), outEnt("arg/x5", 1.5)); add(cxt, inEnt("arg/x6", 1.6)); assertTrue(value(cxt, "arg/x1").equals(1.1)); assertTrue(get(cxt, "arg/x1").equals(1.1)); assertTrue(asis(cxt, "arg/x1").equals(1.1)); // aliasing with an reactive value entry - rvEnt put(cxt, rvEnt("arg/x1", value(cxt, "arg/x5"))); assertTrue(get(cxt, "arg/x1").equals(1.5)); put(cxt, "arg/x1", 300.0); assertTrue(value(cxt, "arg/x1").equals(300.0));
The data context specified with the context operator describes the data that service providers work on. It is a data structure that describes service provider controlled vocabulary (taxonomy) defined by the provider’s developer. Each term in a taxonomy is in one or more parent-child relationship to other terms in the context taxonomy. Conceptually a data context is similar in structure to a files system, where taxonomy paths refer to objects instead to files. The meaning of a hierarchical path is generalization/specification. A requestor submitting a context to a provider has to comply with that taxonomy as it specifies how the context data is interpreted and used by the provider.
Each context entry is considered as a variable, where a taxonomic path is its semantic name associated with its value. There are many types of entries: common entries (ent operator), input entries (inEnt operator), output entries (outEnt operator), input/output enties (inoutEnt operator). Entry declared with a path only has its value undefined, that is Context.none. Understanding the context taxonomy and semantics of entry types a provider can retrieve from its context also all input, output, inout entries, or entries marked with domain specific relationships. Above, the operators context, add, value, get, asis, put illustrate how a context is created and how its data can be used and updated in the service context.
Learn more about data context at the tutorial page Service Contexts. Also review the wide range of context examples in examples/sml/src/test/java/sorcer/sml/contexts.
Exertions
Equipped with the basic SML operators (sig, service, and context) consider a generic rule for modeling collaborative service federations.
service(<name> {, <signature> }, <context> {, <component exertion> } {, <strategy > }, {, <pipe> }):T <T extends Service>;
-
A returned service model is of the Service type and can be referred to Exertion or one of exertion basic types: Task, Job, Block or one of its specific subclasses.
-
A signature format depends on the type of service type and how a provider is initialized. The basic signature format is below, where a <selector> is an operation name (String) of <service type> that is a Java class (AdderImpl.class) or interface type (Adder.class), and a list of arguments of type Arg specifies the QoS of the action defined by the sig operator. Optionally an instance of provider <object instance> can be used instead of <service type>.
sig(<selector>, <service type> | <object instance>, {, <arg> })
-
A service context is specified as a collection of entries with the context operator as shown above. Context can be persisted with SORCER data store provider and shared across providers in exertions.
-
An exertion strategy defined with the strategy operator, e.g., strategy(Flow.PAR, Access.PULL, Provision.YES) tells the SORCER OS to execute component exertions in parallel, use asynchronous coordination of providers executing component exertions, and absent providers in the network for component exertions to be provisioned on-demand.
-
The most common arg for a named remote provider is specified with the prvName operator
prvName("My Provider Name")
-
A component exertion <component exertion> is specified by the service operator as above.
Returning Service Results
There are two ways to execute exertions, by exerting the service model or evaluating the exertion.
-
Exerted service federation returns the exertion with output data context accumulated from collaborating providers and execution trace and exceptions available from collaborating providers:
exert(<exertion> {, entry(path, Object}):Exertion
where entries define a substitution for the exertion’s context associations.
-
Alternatively, an exertion when evaluated returns its output context or result corresponding to the specified result path either in the exertion’s service (SRV type) signature or its data context:
value(<exertion> {, entry(path, Object) }):Object
-
See clarifying examples in
examples/sml/src/test/java/services/Services.java
Exertions: Tasks
task(<name>, { <signature> }, <context>):Task
Example 1. Explicit service provider
In SORCER you can use explicit references to service provider objects (not recommended) as in object-oriented programming.
Object obj = new Date(); Signature s = sig("getTime", obj); assertTrue(value(service("gt", s)) instanceof Long); Object prv = provider(s); assertTrue(prv instanceof Date);
What’s going on here? This service script defines a single service, called gt, and adds a signature s (defined with the sig operator) to it specified by the getTime selector of the provider obj class. When you ask for the value of service gt
value(service("gt", sig("getTime", new Date()));
SORCER executes the service gt, which in turn executes the action by the provider you’ve specified in the signature s. The action is simply an operation getTime to execute by the service provider. That is the simplest service possible, with no arguments that is equivalent to Java method invocation on instance of the Date class.
Most of the examples in this user tutorial are run as JUnit tests found in examples/eol/services.
Example 2. Execution of a service script with a class provider
In Example 1 a service provider is an instance of the Date class. Conceptually in SORCER there is no need to use provider explicit reference to providers. Instead a service types are use that are Java classes or interface.
value(service("gt", sig("getTime", Date.class));
Here SORCER creates the instance of requested provider in the location and time where the service is executed. In SML, a reference to a provider and/or its activity is always a signature.
Example 3. Context-aware local service provider
public interface Adder { public Context add(Context context) throws RemoteException, ContextException, MonitorException; }
Consider the AdderImpl class that implements the context-aware interface Adder.
Signature lps = sig("add", AdderImpl.class); Object prv = provider(lps); assertTrue(prv instanceof AdderImpl); assertFalse(prv instanceof Proxy); // the local service Service as = service("as", lps, context("add", inEnt("arg/x1", 20.0), inEnt("arg/x2", 80.0), result("result/y"))); assertEquals(100.0, value(as));
The signature ‘lps’ defines action of the service with the sig operator and then the declared service las with that signature and data context add created with the context operator. A service context is an associative collection of entries (associations), <path, value> pairs. Paths define the provider’s namespace of data organized by the provider, in that case the AdderImplclass. There are many types of entries. In the Example 3 input entry (defined with inEnt), output entry (defined with outEnt), return value entry (defined with result) are used. The AdderImpl provider finds all input entries and adds up all their values to return the sum in the context at the path reslt/y.
After declaring a signature we can test the instance of the specified provider with the provider operator that returns the provider for all types signatures known in SORCER, both local and remote.
Example 4. Context-aware remote service provider
Signature rps = sig("add", Adder.class); Object prv = provider(rps); assertTrue(prv instanceof Adder); assertTrue(prv instanceof Proxy); // the remote service Service as = service("as", rps, context("add", inEnt("arg/x1", 20.0), inEnt("arg/x2", 80.0), result("result/y"))); assertEquals(100.0, value(as));
Example 4 illustrates a remote service with the identical script syntax as in Example 3. The only difference is in the service type of signature to be now the interface type Adder. As the provider test indicates the provider is a remote Proxy now. In that case SOS finds the provider in the network and return the result of the remote provider activity. When there is no service in the network SOS is capable to provision providers autonomicaly for requests services. Remote providers in SORCER exhibit with respect to requestors three neutralities regarding their location, implementation, and wire protocol. As we have learned so far service providers can specified by service types - no explicit static references are required. So far elementary service have been considered, called elementary service tasks, services with a single signature only.
Review the wide range of task exertions in examples/sml/src/test/java/sorcer/sml/tasks.
Exertions: Batch tasks
batch(<name>, { <signature> }, <context>) : Task
A batch task or simply a batch is the task with multiple signatures that correspond to a pipeline of individual tasks sequentially processing the same-shared context. Processing the context is defined by signatures of PRE type executed first, then the only one SRV signature, and at the end POST signatures if any. The provider defined by the task’s SRV signature manages the coordination of exerting the remaining task providers. When multiple signatures exist with no type specified, by default all are of the PRE type except the last one being of the SRV type. The task mapping can represent a function, a composition of functions, or relations actualized by collaborating service providers determined by the concatenation of task signatures.
Example 4. Batch task defining service concatention of of three services: multiply, add, subtract
// batch for the composition f1(f2(f3((x1, x2), f4(x1, x2)), f5(x1, x2)) // shared context with named paths Task batch3 = batch("batch3", type(sig("multiply", Multiplier.class, result("subtract/x1", Signature.Direction.IN)), Signature.PRE), type(sig("add", Adder.class, result("subtract/x2", Signature.Direction.IN)), Signature.PRE), sig("subtract", Subtractor.class, result("result/y", inPaths("subtract/x1", "subtract/x2"))), context(inEnt("multiply/x1", 10.0), inEnt("multiply/x2", 50.0), inEnt("add/x1", 20.0), inEnt("add/x2", 80.0))); Task out = exert(batch3); assertEquals(get(out, "result/y"), 400.0);
Review the wide range of task batch tasks in examples/sml/src/test/java/sorcer/sml/tasks.
Exertions: Blocks
block(<name>,<exertion> {, <exertion>, <shared context> }) : Block A *block exertion* is a concatenation of component exertions with a shared context that provide a block scope for all executed exertions in the block. There are flow of control operators defining branching and looping: alt (alternative), opt (option), loop (iteration). The operators opt, alt, and loop have similar control flow semantics as those opreators defined in UML sequence diagrams for combined fragments. Block's scope context is usually declared with the `scope` operator. Task t3 = task("t3", sig("subtract", Subtractor.class), context("subtract", inEnt("arg/t4"), inEnt("arg/t5"), result("block/result", Signature.Direction.OUT))); Task t4 = task("t4", sig("multiply", Multiplier.class), context("multiply", inEnt("arg/x1", 20.0), inEnt("arg/x2", 10.0), result("arg/t4", Signature.Direction.IN))); Task t5 = task("t5", sig("add", Adder.class), context("add", inEnt("arg/x3"), inEnt("arg/x4"), result("arg/t5", Signature.Direction.IN))); Block block = block("block", t4, t5, t3, scope( inEnt("arg/x1", 10.0), inEnt("arg/x2", 50.0), inEnt("arg/x3", 20.0), inEnt("arg/x4", 80.0))); Block result = exert(block); assertEquals(value(context(result), "block/result"), 100.00);
Review the wide range of block exertions in examples/sml/src/test/java/sorcer/sml/blocks.
Exertions: Jobs
job(<name> [, <signature> ], <context>, <exertion> {, <exertion> }) : Job
A job represents a collaboration of multiple provider actions (composite exertion) It is the exertion with a single input context and a nested composition of component exertions each with its own input context. A job represents a mapping that describes how input associations of job’s context and component contexts relate, or interact, with output entries (associations) of those contexts. Tasks do not have component exertions but may have multiple signatures, unlike jobs that have at least one component exertion and a signature is optional. The default job signature is of the Jobber type, network execution by SOS. Pipes declared in jobs defined daya flow between data contexts of component exertions (services).
Service t3 = service("t3", sig("subtract", Subtractor.class), context("subtract", inEnt("arg/x1"), inEnt("arg/x2"), outEnt("result/y"))); Service t4 = service("t4", sig("multiply", Multiplier.class), context("multiply", inEnt("arg/x1", 10.0), inEnt("arg/x2", 50.0), outEnt("result/y"))); Service t5 = service("t5",sig("add", Adder.class), context("add", inEnt("arg/x1", 20.0), inEnt("arg/x2", 80.0), outEnt("result/y"))); // Service Composition j1(j2(t4(x1, x2), t5(x1, x2)), t3(x1, x2)) Service job = service("j1", strategy(Provision.YES) service("j2", t4, t5, strategy(Flow.PAR, Access.PULL)), t3, pipe(out(t4, "result/y"), in(t3, "arg/x1")), pipe(out(t5, "result/y"), in(t3, "arg/x2")));
The service job is composed of two exertions: j2, t3 and the exertion j2 consists of tasks t4 and t5. Operators pipe and strategy define the control context of job exertions.
Review the wide range of job exertions in examples/sml/src/test/java/sorcer/sml/jobs.
SML examples in the SORCER Project
- Collections used in SML
- sml/src/test/java/sorcer/arithmetic/collections
- Service signatures, providers, services (exertions), and context models (contexts and models) in SML
- sml/test/java/sorcer/sml/services
- Intro to service provider/requestor development
- service/src/main
- service/src/test
- service/src/netlet and
- worker/src/main/java/sorcer/worker/provider
- worker/src/main/java/sorcer/worker/requestor
- worker/test/java/sorcer/worker/tests
- Arithmetic providers and requestors, contexts and exertions as providers
- sml/main/java/sorcer/arithmetic/provider
- sml/main/java/sorcer/arithmetic/requestor
- sml/main/netlets
- Service contexts
- sml/test/java/sorcer/sml/contexts
- Exertions: tasks, jobs, and blocks
- sml/test/java/sorcer/sml/tasks
- sml/test/java/sorcer/sml/jobs
- sml/test/java/sorcer/sml/blocks
- Data models (data contexts), entry models and service models
- sml/test/java/sorcer/sml/contexts
- Par-models, providers and agents
- pml/main/java/sorcer/pml/model
- pml/main/java/sorcer/pml/provider
- pml/test/java/sorcer/pml/modeling