figure \cftpagenumbersofftable
A type language for message passing component-based systems
Abstract
Component-based development is challenging in a distributed setting, for starters considering programming a task may involve the assembly of loosely-coupled remote components. In order for the task to be fulfilled, the supporting interaction among components should follow a well-defined protocol. In this paper we address a model for message passing component-based systems where components are assembled together with the protocol itself. Components can therefore be independent from the protocol, and reactive to messages in a flexible way. Our contribution is at the level of the type language that allows to capture component behaviour so as to check its compatibility with a protocol. We show the correspondence of component and type behaviours, which entails a progress property for components.
1 Introduction
Code reusability is an important principle to support the development of software systems in a cost-effective way. It is a key principle in Component-Based Development (CBD) [16], where the idea is to build systems relying on the composition of loosely-coupled and independent units called components.
The motivations behind CBD are, on the one hand, to increase development efficiency and lower the costs (by building a system from pre-existing components, instead of building from scratch), and on the other hand, to improve quality of the software for instance to what concerns software errors (components can be tested over and over again in different contexts). Consider, for example, microservices (see, e.g., [9]) that have been recently adopted by massively deployed applications such as Netflix, eBay, Amazon and Uber, and that are reusable distributed software units. In such a distributed setting, composing software elements necessarily involves some form of communication scheme, for instance based on message passing.
In order for the functionality to be achieved, communication among components should follow a well-defined protocol of interaction, that may be specified in terms of some choreography language like, for example, WS-CDL [19] or the choreography diagrams of BPMN [18]. A component should be able to carry out a certain sequence of I/O actions in order to fulfil its role in the protocol. One way to accomplish this is to implement a component in a way that prescribes a strict sequence of I/O actions, that should precisely match the actions expected by the protocol. However, this choice interferes with reusability, since such a component can be used only in an environment that expects that exact sequence of communication actions. For instance, if a component receives an image and outputs its classification just once, what will happen if we need to use this component in a context that requires the classification is sent multiple times?
In contrast, a more flexible design choice inspired by reactive programming is to design components so that they can respond to external stimulus without any specific I/O sequence. The reactive programming principle for building such components is to consider that as soon as the data is available, it can be received or emitted. For example, we can design a component that is able to output a classification after receiving an image, as long as required. In such a way, reusability is promoted since such components can be used in different environments thanks to the flexibility given by the reactive behaviour. However, such a flexibility at the composition level may be too wild if all components are able to send / receive data as soon as it is available. Hence, there is the need to discipline the interactions at the level of the environment where the composition takes place. What if, for example, we have different images that need to be classified and the classifying component is continuously emitting the result for the first image?
Carbone, Montesi and Vieira [6] proposed a language that supports the development of distributed systems by combining the notions of reactive components with choreographic specifications of communication protocols [17]. The proposal considers components that can dynamically send / receive data as soon as it is available, while considering that an assembly of components is governed by a protocol. Hence, among all the possible reactions that are supported by the composed components, the only ones that will actually be carried out are the ones allowed by the protocol. A composition of components defines itself a component that can be further composed (under the governance of some protocol) also providing a reactive behaviour. This approach promotes reusability thanks to the flexibility of the reactive behaviour. For instance, by abstracting from the number of supported reactions, e.g., if a component can (always) perform a computation reacting to some data inputs, then it can be used in different protocols that require such computation to be carried out a different number of times; by abstracting from message ordering, e.g., if a component needs some values to perform a computation, it may be used with any protocol that provides them in any order.
Component implementations should be hidden, so it shouldn’t be necessary to inspect the inner workings in order to asses if it is usable in a determined context for the purpose of off-the-shelf substitution or reuse. Hence, a component should be characterised with a signature that allows checking its compatibility when used in a composition. In particular, it must be ensured that each component provides (at least) the behaviour prescribed by the protocol in which the component participates. Carbone et al. [6] propose a verification technique that ensures communication safety and progress. However, the approach requires checking the implementation of components each time the component is put in a different context, i.e., each time that the component is used “off-the-shelf” we need to check if the reactions supported by the component are enough to implement its part in the protocol.
In this work we consider a different approach, avoiding the implementation check each time a component is to be used. Firstly, we introduce a type language that characterises the reactive behaviour of components. Secondly, we devise an inference technique that identifies the types of components, based on which we can verify whether the component provides the reactive behaviour required by a context. The motivation is in tune with reusability: once the component’s type is identified, there is no further need to check the implementation, because the type is enough to describe “what the component can do”.
Our types specify the ability of components to receive values of a prescribed basic type. Moreover, they track different kinds of dependencies, for instance that certain values to be emitted always (for each output) require a specific set of inputs (dubbed per each value dependencies). Our types can also describe the fact that a component needs to be, in some sense, initialised by receiving specific values before proceeding with other reactive behaviour (dubbed initial dependencies). Furthermore, our types also identify constraints on the number of values that a component can send. Finally, we ensure the correctness of our type system by proving that our extraction procedures are sound with respect to the semantics of the Governed Components (GC) language [6], considering here the choice-free subset of the GC language and leaving a full account of the language to future work. Moreover, we ensure that whenever a type of a component prescribes an action, a component will not be stuck, i.e., it will eventually carry out the matching action.
The rest of the paper is organised as follows. We first present the GC language in Section 2. Then in Section 3 we intuitively introduce our type language through a motivating example based on AWS Lambda [2] where we point out different scenarios that might occur while composing components and how our types allow to describe certain behavioural patterns. Section 4 introduces the syntax and the semantics of our types. Then, we define the type extraction for base components in Section 4.1, whereas the type extraction for composite components in Section 4.2. In Section 4.3 we present our results. Section 5 concludes, discusses related work and gives some future issues.
2 Background: Governed Components Language
In this section we briefly introduce the GC language, focusing on the main points that allow to grasp the essence of the model and to support a self-contained understanding of this paper. We refer the interested reader to [6] for a full account of the language. The syntax of the (protocol choice-free fragment of the) GC language is given in Table 1. There are two kinds of components (): base and composite. Both kinds interact with the external environment by means of input and output ports exposed as the component’s interface. Besides of the interface, components are defined by their implementation.
Components | Local Binders | Protocol | |||
---|---|---|---|---|---|
(base) | (communication) | ||||
(composite) | (recursion) | ||||
(recursion variable) | |||||
(termination) | |||||
Role assignments | Distribution Binders | Forwarders | |||
In the case of a base component the implementation is given by the list of local binders (). A local binder specifies a function, denoted as , which is used to compute the output values for port relying on values received on list of (input) ports . So, we say that component’s ability to output a value may depend on the ones received, where instead, components are always able to receive values.We abstract from the definition of such functions and assume them to be total. Received values are processed in a FIFO discipline, so queues are added to the local binders at runtime (noted as ). Each element () in a queue () is a store defined as a partial mapping from input ports to values (, where in the oldest values received are stored, in the second-oldest values, and so on and so forth up to ). E.g., if and the component receives and in that order on port and , and on port . The queue at this point has three mappings where two are “complete” to compute the function and one is not.
The implementation of a composite component, represented by , is an assembly of subcomponents whose interaction is governed by a protocol (). The set of subcomponents are given in together with their roles in the interaction (e.g., denotes that component is assigned to role ). Composite components also specify a list of (distribution) binders () that provide an association between the messages exchanged in the protocol () and the ports () of the components (e.g., states that a message with a label is emitted on port of the component assigned to role , and to be received on port of the component assigned to role ). Ports are uniquely associated to message labels () in such way that each communication step in the protocol has a precise mapping to a port, i.e., all values emitted on a port will be carried in messages with the same label and all values received on a port will be delivered in messages with the same label. E.g., every time a value is emitted from some port it will be carried in a message labelled with e.g., and a value delivered on some port is delivered on a message with the same label . Some other label cannot be attached to , otherwise the association would not be unique. The message label (or some other) cannot be attached to otherwise the association would not be unique. Finally, subterm is used to specify the externally-observable behaviour: the (only interfacing) subcomponent responsible for the interaction with the external environment is identified (by its role ) and the respective connection between ports is provided by forwarders (). The idea is that values received on the (input) ports of the composite component are directly forwarded to the (input) ports of the interfacing subcomponent, and values emitted on the (output) ports of the interfacing subcomponent are forwarded to the (output) ports of the composite component. For example, the term is for forwarding an input, and the term is for forwarding an output ( and are the ports of the composite component).
Protocol specifications prescribe the interaction among a set of parties identified by roles. Communication term specifies that role sends the message labelled to the (nonempty) set of roles , after which the protocol continues as specified by . The difference between this work and [6] is the absence of branching. Then we have terms and for specifying recursive protocols. Finally, term defines the termination of the protocol.
We now present the operational semantics of the GC in terms of a labelled transition system (LTS). We denote by that a component evolves in one computational step to , where observations are captured by labels defined as follows . Transition label represents an input on port of a value , label captures an output on port of a value , and label denotes an internal move.
The rules that describe the behaviour of components are presented in two parts, addressing base and composite components separately. We present only the main rules, the full semantics can be found in [6].
Rules and that are given in Table 2 capture base component behaviour, and are defined relying on transitions exhibited by local binders, denoted . Rule states that if local binders can exhibit an output of value on port , where is part of the component’s interface, then the corresponding output can be exhibited by the base component. Rule follows the same lines.
Notice that the transition of the local binder specifies a final configuration which is accounted for in the evolution of the base component. We omit here the rules for local binders (see [6]) and provide only an informal account for them. Essentially, a (runtime) local binder is always receptive to an input : if is not used in the function () then value is simply discarded; otherwise, the value is added to the (oldest) entry in mapping queue that does not have an entry for (possibly originating a new mapping at the tail of ). All local binders in synchronise on an input, so each local binder will store (or discard) its own copy of the value. Instead, local binder outputs are not synchronised among them: if a local binder outputs a value, other local binders will not react. For this to happen, the oldest mapping in queue must be complete (i.e., assign values to all of ) at which point function may be computed, the result which is then carried in the transition label (i.e., the in ).
We now introduce the rules that capture the behaviour of the composite components, displayed in Table 3. Notice that a composite component may itself be used as a subcomponent of another composition (of a “bigger” component), and base components provide the syntactic leaves. Rules and capture the interaction of a composite component with an external environment, realised by the interfacing subcomponent. The role assignment captures the relation between component and role , which is specified as the interfacing role (). Rule allows for the interfacing component to send a value to the external environment via one of the ports of the composite component (). Notice that the connection between the port of the interfacing component () and the port of the composite component () is specified in a forwarder (). Rule follows the same lines to model an externally-observable input. Rule allows for internal actions in a subcomponent (), where the final configuration () is registered in the final configuration of the composite component.
Rules and capture the interaction among subcomponents of a composite component. Rule addresses the case when a component is sending a message to another one. The premises, together with role assignment , establish the connection among sender component , the component port , sender role , and message label . Premise says that the sender component () can perform an output of value on port . Premise says that the distribution binders specify the (unique) relation between port of sender role and message label (receiver role and associated port are not important here). The last premise realises the component governing the protocol, i.e., saying that the communication is only possible if the protocol prescribes it. Namely, the premise says that the protocol exhibits an output of a value carried in message from role . The rules for protocol transitions are presented in [6]. Naturally which semantics has an impact on our technical development (namely regarding end-point projection in local protocols), but to some extent can be addressed in a modular way (i.e., up to the existence of the end-point projection). Notice that the transitions of component and protocol specify final configurations and which are accounted for in the evolution of the composite component.
Rule is similar, but instead of message sending, it addresses the case when a subcomponent receives a message from another subcomponent. The premises are equivalent to the ones for Rule , but now regard reception. Namely, by saying that receiving component can exhibit the respective input transition, that the distribution binder specifies the relation of message label with receiver role and port , and that protocol prescribes the input of a value.
3 Motivating example: microservices for Image Recognition System
In order to further motivate GC and also to introduce our typing approach, we now informally discuss an example inspired by a microservices scenario [2] that addresses an Image Recognition System (). The basic idea is that users upload images and receive back the resulting classification. Moreover, users can get the current running version of the system whenever desired. The is made of two microservices, and (), that interact according to a predefined protocol.
The task is achieved according to the following workflow: sends the loaded by a user to to be processed. When service finishes its , it sends the as the result of the to . We model the scenario in the GC calculus by assigning to each microservice the corresponding role and using components to represent them. We assign role to component and role to component , where and are base components. Interaction between these two components is governed by global protocol , that can be described as: . This (the part of ) protocol exactly specifies the workflow described above: sends an to () that answers with the computed (). If we add the termination () we obtain (complete ) protocol , which may be described as a one-shot protocol, since the interaction is over () after the components exchange the two messages.
We obtain composite component by assembling and together with protocol that governs the interaction. Below, we show how it is possible to graphically represent component , where we represent and as its subcomponents:
![[Uncaptioned image]](https://cdn.awesomepapers.org/papers/1bca64ac-5b41-4aaa-a1e1-c8f11e9fc5c7/comp.jpg)
The subcomponent is the interfacing component (hence is the only one connected to the external environment via forwarders). We can specify in the GC language as
As previewed in the graphical illustration, from the specification we can see that component has two input ports (), three output ports (), and three local binders that at runtime are equipped with queues (, and empty queue given that the respective binder does not use any input ports). Notice that the queues are only required at runtime and are initially empty.
The idea of our type description is to provide an abstract characterisation of component’s behaviour. Types provide information about the set of input ports, namely the types of values that can be received on them, and about the output ports, namely regarding their behavioural capabilities. In particular, for each output port there are constraints which comprise three pieces of information: what type of values are emitted; what is the maximum number of values that can be emitted; and what are the dependencies on input ports, possibly including the number of currently available values that satisfy the dependency at runtime.
Informally, the type of announces the following: In the two input ports and the component can receive an and a , respectively ({). Also, the type says in the component can emit s and it can do so an unbounded number of times (denoted by ) as the underlying local binder imposes no boundary constraints. In particular, the local binder can send an as soon as one is received in . Hence, we have a per each value dependency of on . Formally, we write this constraint as , where is the number of values received on that are available to be used to produce the output on . We may describe constraint in a similar way. In constraint there are no dependencies from input ports specified, hence the reading is only that a can be emitted an unbounded number of times. We may specify the type of as:
Composite component is an assembly of two base components and whose communication is governed by global protocol . The description of in GC language is the following:
where is the already described one-shot protocol
Interfacing component forwards the values from/to the external environment as specified in the forwarders (). The forwarding implies that the characterisation of ports , and in the type of relies on one of the ports , and , respectively, in the type of .
The type of then says that it can always input on values of type accordingly to the input receptiveness principle. The constraint for is the same as for since does not depend on the protocol (in fact it has no dependencies). However, this is not the case for : in order for a of an image to be forwarded from there is a dependency (identified in ) on port . Furthermore, component will only receive a value on accordingly to the protocol specification, in particular upon the second message exchange. Hence, there is also a protocol dependency since the first message exchange has to happen first, so there is a transitive dependency to an being sent in the first message exchange, emitted from port of component . Finally, notice that depends on which is linked by forwarding to port of component , thus we have a sequence of dependencies that link to .
Since we have a one-shot protocol, the communications happens only once, which implies that one is produced for the first received. We therefore consider that the dependency of on is initial (since one value suffices to break the one-shot dependency), and that the maximum number of values that can be emitted on is 1. This constraint is formally written as . The constraint for is , where the set of dependencies is empty, i.e., it does not depend on any input. We then have the following type for component :
Let us now assume a recursive version of protocol
is used instead (i.e., ). The idea now is that for each received a is produced. So, may be emitted on an unbounded number of times and the dependency of on is of a per each kind. Notice that the chain of dependencies can be described as before, but the one-shot dependency from before is now renewed at each protocol iteration.
The constraint for in this settings is , where captures the number of values received on that are currently available to produce the outputs on . The constraint for is the same as in the previous case. We then have that the type of is
Imagine that is now a composite component that has an initialisation phase such that, first it receives a message about what kind of classification is required (e.g., “classify the image by the number of faces found on it”), then it sends it to , after which the uploading and classification of the images can start (all other characteristics remain). Let be the port of on which this message is received. Let us consider the following protocol
where after component sends the required kind of classifications (labelled as ), the communication between and is governed by a recursive protocol as described in the previous example. The type of the component is similar to the type from the previous example, but now announces that the output on requires an initial value to be received on port , as the image classification process can only start after that. We then have the type of
4 A type language for the components
In this section we define the type language that captures the behaviour of components in an abstract way, starting with the presentation of the syntax which is followed by the operational semantics. Then we present two procedures that define how to extract the type of a component. The first procedure is for base, and the second one is for composite components.
Syntax
The syntax of types is presented in Table 4 and some explanations follow. A type consists of two elements: a (possibly empty) set of input ports, where each one is associated with a basic type (i.e., int, string, etc.), and a (possibly empty) set of constraints C, one for each output port. Basic types (ranged over by ) specify the type of the values that can be communicated through ports, so as to ensure that no unexpected values arise. Each constraint in C contains a triple of the form , which describes the type () of values sent via , the capability (B) of and the dependencies (D) of on the input ports. The set of constraints C is ranged over (likewise other syntactic categories like , B, ). Capability B identifies the upper bound on the number of values that can be sent from the output port: a natural number denotes a bounded capability, whereas an unbounded one. Dependencies are of two kinds: per each value dependencies are of the form and initial dependencies are given by . A dependency says that each value emitted on requires the reception of one value on , and furthermore provides the (runtime) number of values available on (hence, initially ). Instead, a dependency says that initially depends on a (single) value received on , hence the dependency is dropped after the first input on .
Note that there are only two kinds of dependencies: a per each value dependency and an initial one. Since we aim at static typing, the dependencies that appear after the extraction of a type are either or , but for the sake of showing our results, we need to capture these values in the evolution of the types. So, we need to capture the number of values available on the input ports, hence we have dependencies of the kind (for ).
Remark.
In this work we investigate the mathematical model for the purpose of showing our results, but we may already point towards practical applications. In particular, for the purpose of the (static) type verification we are aiming at, the counting required for the theoretical model would not be involved and the component type information available for developers would be as follows:
-
•
set of input ports with their basic types
-
•
set of constraints for each output port with the following information
-
1.
the basic type associated to the output port
-
2.
one of two possibilities for output port capability: bounded or unbounded
-
3.
one of two possibilities for each kind of dependency: per each value or initial
-
1.
Hence, for the sake of static type checking and from a developers perspective, apart the expected information regarding basic (value) types, the type information would be y:bounded
or y:unbounded
to what concerns output port capabilities and x:pereach
or x:initial
to what concerns dependencies.
Types and input interfaces | Dependency kinds | Boundaries |
Constraints | Dependencies | |
Semantics
We now define the operational semantics of the type language, that is required to show that types faithfully capture component behaviour. The semantics is given by the LTS shown in Table 5. There are four kinds of labels described by the following grammar: . Label denotes an input on ; whereas, label denotes an input of a value of type ; then, label represents an output of a value of type ; finally, captures an internal step.
We briefly describe the rules shown in Table 5. Rules [T1,T2,T3] describe inputs of a (single) constraint, while [T4, T5, T6] capture type behaviour. Rule [T1] says a constraint for can receive (and discard) an input on in case does not depend on , i.e., if is not in the domain of D (), leaving the constraint unchanged. Rule [T2] addresses the case of an initial dependency, where after receiving the value on the dependency is removed. Rule [T3] captures the case of a per each value dependency, where after the reception the number of values available on for is incremented.
With respect to type behaviour, Rule [T4] says that the type can exhibit an internal step and remain unchanged, used to mimic component internal steps (which have no impact on the interface). Rule [T5] states that if all type constraints can exhibit an input on and is part of the type input interface, then the type can exhibit the input on considering the respective basic type. Notice that rules [T4, T5, T6] say that constraints can always exhibit an input (simply the effect may be different). Finally, Rule [T6] says that if one of the constraints has all of the dependencies met, i.e., has at least one value for each for which there is a dependency, and also that the boundary has not been reached (i.e., it is greater than zero), then the type can exhibit the corresponding output implying the decrement of the boundary and of the number of values available in dependencies. Notice that in order for a port to output a value, there can be no initial dependencies present (which are dropped once satisfied), only per each value dependencies.
In the following example and in the rest of the paper (where appropriate) we adopt the following notation: abbreviates the type, abbreviates the type and abbreviates the type.
Example 4.1.
We revisit the type of component shown in Section 3
for some and . Recall also type that may evolve upon the reception of an input on as follows:
The type language serves as a means to capture component behaviour, and types for components may be obtained (inferred) as explained below. The results presented afterwards ensure that when the type extraction is possible, then each behaviour in the component is explained by a behaviour in the type, and that each behaviour in the type can eventually be exhibited by the component.
4.1 Type extraction for base components
We describe the procedure that allows to (automatically) extract the type of a component, focusing first on the case of base components, remembering their reactive flavour. The goal is to identify the basic types associated to the communication ports, as well as the dependencies between them, while checking that their usage is consistent throughout.
In order to extract the type of a base component we need to define two auxiliary functions. First, we assume that from each function used in a local binder, we can infer the respective function type. We introduce the notation to represent a mapping from basic elements (such as values, ports, or functions) to their respective types. We also use for lists of elements in which case we obtain the list of respective types (e.g., ). Second, given a local binder , we need to count the number of values that has available at runtime for each of the ports in . This corresponds to the number of elements in that have a mapping for a port to a value, which we denote by defined as follows. Let be the set of ports and a set whose elements are the lists of mappings from ports to values. Then function is defined as follows:
Notice that mappings in are handled following a FIFO discipline, so the first (oldest) mappings are the ones that need to be accounted for. We may now define our type extraction procedure for base components:
Definition 4.1 (Type Extraction for a Base Component).
extractbase
Let be a base component, where . If there exists such that and and provided that for any and that for any then the extracted type of the base component is where and
In \threfextractbase the list of local binders is specified in such a way that each function (), its parameters () and the list of mappings () are indexed with the output port that is associated to them (), so as to allow for a direct identification. Moreover, notice that each list of arguments (of function ) is a permutation of list , as otherwise they would be undefined. Notice also that every output port of the interface of the component has a local binder associated to it and that there is no local binder such that is not part of the component interface, i.e., we do not type components that have undefined output ports or that declare unused local binders, respectively. We also rely in \threfextractbase on (the existence of) to ensure consistency. Namely, we consider provides the list of basic types for the input ports () and for the output ports (). Then, we require that , for each , specifies the function type where the return type matches the one identified for (i.e., ). Furthermore, we require that the types of the parameters given in the function type () match the ones identified for the respective (permutation of) input port parameters ().
We then have that the extracted type of a base component is a composition of two elements. The first one () is a set of input ports which are associated with their basic types. The second one is a set of constraints C, one for each output port and of the form . The constraint specifies the basic type () which is associated to the output port, and the maximum number of values that can be output on is unbounded (), since local binders can potentially perform computations indefinitely. The third element of the constraint () is a set of per each value dependencies (of port ) on the input port parameters , capturing that each value produced on depends on a value being received on all of the ports in . Notice that the number of values that has available (at runtime) for each in is given by .
From an operational perspective, \threfextractbase can be implemented by first considering the type inferred for the functions in the local binders and then propagating (while ensuring consistency of) this information.
Example 4.2.
Consider our running example from Section 3, in particular, component specified as .
Let us take such that and , and . We know that function takes an () and gives an in return, hence . Similarly, we also know that function is typed as . Function does not have any parameters hence . The extracted set of input ports with their types is . Assume that the component is in the initial (static) state, so the queues of lists of mappings are empty (i.e., ). Hence, we have that and . The extracted set of constraints is then and the extracted type of the component is .
4.2 Type extraction for composite components
Extracting the type of a composite component is more challenging than for a base component. The focus of the extraction procedure is on the interfacing subcomponent, which interacts both via forwarders and via the protocol. For the purpose of characterising how components interact in a given protocol, we introduce local protocols which result from the projection of a (global) protocol to a specific role that is associated to a component. We reuse the projection operation from [6], where message labels are mapped to communication ports (thanks to distribution binders ) and also to basic types that describe the communicated values (that can be inferred from the ones of the ports). The syntax of local protocols is:
.
Term denotes a reception of a value of a type on port , upon which protocol is activated. Term describes an output in similar lines. Then we have standard constructs for recursion and for specifying termination (). Our local protocols differ from the ones used in [6] since here we only consider choice-free global protocols. To simplify the setting, we consider global protocols that have at most one recursion (consequently also the projected local protocols). We also consider that message labels can appear at most once in a global protocol specification (up to unfolding of recursion), hence also ports occur only once in projected local protocols (also up to unfolding).
We omit the definition of projection and present the intuition via an example.
Example 4.3.
Let be the (one-shot) protocol from Section 3 and let be a function that given a list of a message labels returns a list of their types.
Then, the projection of protocol to role , denoted by is protocol and the projection of to role is local protocol , where ports are obtained via distribution binders . Essentially, the local protocol of describes that first it emits an image on and then receives a classification on , and the local protocol of says that it first receives an image on and then outputs a result of a classification on .
We introduce some notation useful for the definition of the type extraction for composite components. We use the language context for local protocols (excluding recursion), denoted by , so as to abstract from the entire local protocol and focus on specific parts and we define it as: . We denote the set of ports appearing in a local protocol by and by the set of ports that occur in a recursion (e.g. in for recursion ). Considering a list of forwarders , we define two sets: by we denote the set of (internal) input ports and by the set of (internal) output ports which are specified in (e.g., if then ).
We now introduce the important notions that are used in our type extraction, namely that account for values flowing in a protocol and for the kinds of dependencies involved in composite components. Finally, we address the boundaries for the output ports.
Values flowing
Our types track the dependencies between output and input ports, including per each value dependencies that specify how many values received on the input port are available to the output port. As discussed in the previous section, for base components this counter is given by the number of values available in the local binder queues. For composite components, as preliminary discussed in Section 3, per each value dependencies might actually result from a chain of dependencies that involve subcomponents and the protocol. So, in order to count how many values are available in such case, we need to take into account how many values are in the subcomponents (which is captured by their types) and also if a value is flowing in the protocol. We can capture the fact that a value is flowing by inspecting the structure of the protocol. In particular we are interested in values that flow from to when an output on precedes an input in in a recursive protocol, hence when the protocol is of the form . The value is flowing when the output has been carried out but the input is yet to occur, which we may conclude if the protocol is also of the form where . We denote by that there is a value flowing from to in , in which case , otherwise . We will return to this notion in the context of the extraction of the dependencies of the output ports, discussed next.
Kinds of dependencies
Composite components comprise two kinds of dependencies between output ports and input ports, illustrated in Figure 2 and Figure 2, which are dubbed direct and transitive, respectively.


We gather the set of direct dependencies, i.e., when external output ports directly depend on external input ports (see Figure 2), in which is defined as follows:
Hence, in we collect all the dependencies for given in (internal) constraint C whenever both ports are external and preserving the kind of dependency so as to lift it to the outer interface.
For transitive dependencies (see Figure 2) to exist there are three necessary conditions. The first condition is to have in the description of a local protocol at least one output action, say on port , that precedes at least one input action, say on port . The second condition is that such output port depends on some external input port and the third condition is that there exists some external output port that depends on the input on . In such cases, we say that depends on in a transitive way.
We introduce a relation that allows to capture the first condition above. Let be the local protocol that is prescribed for an interfacing component. Two ports and are in relation for some local protocol if and where as follows: if and ; if and ; if . We distinguish three cases: when both the output and the input are non-repetitive, when only the input is repetitive, and when both the output and the input are repetitive.
We may now characterise the transitive dependencies. Let be the composite component, the type of interfacing component and its local protocol. The set of transitive dependencies on , denoted , is defined relying on an abbreviation as follows:
In we gather a conjunction of conditions that must always hold in order for a transitive dependency to exist: namely that the (internal) constraint C specifies dependencies between and and between and and also that and are external ports while precedes in the protocol. To simplify presentation of the definition of we rely on the (direct) implicit matching in of the several mentioned elements.
There are two kinds of transitive dependencies that are gathered in , namely initial () and per each value (). For initial dependencies there are two separate cases to consider. The first case is when the protocol specifies that the output on is non-repetitive (), hence will be provided only once. Condition says that no values are already available for that initial output to take place (internally to the component that provides them as specified in ), hence either or .
The second case for an initial transitive dependency is when both and are repetitive in the protocol () but at least one of the internal dependencies (between and and between and , given by and respectively) is an initial dependency. This means that, regardless of the protocol, such a dependency is dropped as soon as a value is provided which implies that the transitive dependency is also dropped. Since is at the beginning of the dependency chain, if it is initial then no further conditions are necessary. However, if is initial we need to ensure that there is no value already flowing () or already available to be output on (), since only in such case (an initial) value is required from the external context (i.e., otherwise if or then the chain of dependencies is already “internally” satisfied).
Finally, we have the case of per each value transitive dependency, that can only be when both and are repetitive in the protocol () and internal dependencies and are both per each value dependencies ( and ), which means that the dependency chain is persistent. The number of values available of (external) for is the sum of the values available in the internal dependencies ( and ) plus one if there is a value flowing (zero otherwise). Notice that the definition of value flowing presented previously focuses exclusively in the case when and are repetitive in the protocol, since this is the only case where values might be flowing and the dependency is still present in the protocol structure (i.e., holds). In contrast, a dependency for is no longer (structurally) present as soon as the value is flowing (i.e., a non-repetitive no longer occurs in the protocol after an output).
It might be the case that one output port depends in multiple ways on the same input port. For that reason we introduce a notion of priority among dependencies, denoted by that gives priority to per each value dependencies (with respect to “initial”). The priority builds on the property that if multiple per each value dependencies (including direct and transitive) are collected (e.g., ) then the number of available values specified in them is the same (i.e., ). The list of dependencies for port is then given by the (prioritised) union of direct and transitive dependencies:
Boundaries
The last element that we need to determine in order to extract the type of a composite component is the boundary of output ports. The type of the interfacing component already specifies a (internal) boundary, however this value may be further bound by the way in which the component is used in the composition. In particular, if an output port depends on input ports that are not used in the protocol nor are linked to external ports, then no (further) values are received in them and the potential for the output port is consequently limited. We distinguish three cases for three possible limitations:
In and we capture the case when there is a dependency on a port that is not used in the protocol () nor linked externally (), where the difference is in the kind of dependency. For per each value dependencies (if any), the minimum of the internally available values is identified as the potential boundary, while for initial dependencies (if present) the potential boundary is zero (or the empty set). In we capture a case similar to where the port is used in the protocol but in a non-repetitive way, hence only one (further) value can be provided.
The final boundary determined for , denoted by , is the minimum number among the internal boundary of (i.e., B if ) and possible boundaries and described above.
We may now present the definition of type extraction of a composite component relying on a renaming operation. Since the type extraction of a composite component focuses on the interfacing subcomponent, we single out the ports that are linked via forwarders to the external environment. To capture such links, we introduce renaming operation that renames the ports of the interfacing subcomponent to the outer ones by using the forwarders as a guideline. For example, if we have that than .
Definition 4.2 (Type Extraction for a Composite Component).
compositeextraction
Let be a composite component and the local protocol for component . If is the type of component , then the extracted type from and is
where:
.
Example 4.4.
Let us extract the type of component from Section 3 considering protocol . The type of interfacing component is
We have that local protocol is and sets of external ports and , where , and . This immediately gives us the set of input ports that is in the description of the type of component which is .
Let us now determine the constraints of the output ports. Since port has no dependencies also port will not have any, and moreover has the same boundary (). So, the extracted constraint for will be , which is . Port instead depends on port which is used in the protocol (). Since the protocol is not recursive we have the consequent limited boundary (case explained above), namely the boundary of is . Furthermore. we have that and that has per each value dependency . If then does not transitively depend on , otherwise there is an initial dependency. Let us consider the initial (static) state where no image has been receive yet, i.e., . In such case we have that the resulting constraint for is , which after renaming for is . So, the extracted type of is the following
4.3 Type Safety
In this section we present our main results that show a tight correspondence between the behaviour of components and of their extracted types. Apart from the conditions already involved in the type extraction, for a component to be well-typed we must also ensure that any component that interacts in a protocol can actually carry out the communication actions prescribed by the protocol.
For this reason we introduce the conformance relation, denoted by , that asserts compatibility between the type of a component and the local protocol that describes the communication actions prescribed for the component. For the purpose of ensuring compatibility, in particular for the interfacing component, we also introduce an extension of our type language, dubbed modified types (see Appendix A). The idea for modified types is to allow to abstract from input dependencies from the external environment, namely by considering such dependencies can (always) potentially be fulfilled, allowing conformance to focus on internal compatibility. By we denote the modified type that results from abstracting such external dependencies in , relying on forwarders , namely by considering per each value dependencies for external input ports are unbounded () and dropping initial dependencies. The rules for the semantics of modified types are the same as the ones shown in Section 4, the only implicit difference for modified types is that decrementing an unbounded dependency has no effect.
The definition of the conformance relation (see Appendix B) is given by induction on the structure of the local protocol and it is characterised by judgments of the form , where is a type environment that handles protocol recursion (i.e., maps recursion variables to modified types). We report and comment here only on the rules for input and output:
Rule [] states that a modified type is conformant with protocol , if can input a value of type on port and if continuation is conformant with the continuation of protocol . Rule [] is similar but deals with the output of a value on port .
We can now formally define when a component has type , in which case we say is well-typed.
Definition 4.3.
Let be a component, we say that has a type , denoted by :
-
1.
If is a base component, when is obtained by \threfextractbase.
-
2.
If then when
-
•
;
-
•
is extracted type from and by \threfcompositeextraction;
-
•
;
-
•
Notice that the definition relies on modified types for conformance, but for any type not associated with the interfacing component we have that since there can be no links to external ports (assuming that all ports have different identifiers).
We can now our type safety results given in terms of Subject Reduction and Progress, which provide the correspondence between the behaviours of well-typed components and their types. In the statements we rely on notation that represents , or and that represents , or .
Theorem 4.1 (Subject Reduction).
SubjectReduction If and and has type then and .
Proof.
By induction on the derivation of . ∎
SubjectReduction says that if a well-typed component performs a computation step to , then its type can also evolve to type which is the type of component . Moreover, the theorem ensures that if carries out an input or an output of a value , type performs the corresponding action at the level of types. \threfSubjectReduction thus attests that well-typed components always evolve to well-typed components, and furthermore that any component evolution can be described by an evolution in the types.
The progress result does not describe a strong correspondence like for Subject Reduction since we need to abstract from internal computations in components. For that reason, in the Progress statement we rely on to denote a sequence of transitions , i.e, that component may perform a sequence of internal moves, then an I/O action, after which another sequence of internal moves leading to .
Theorem 4.2 (Progress).
Progress If and and then is the type of a value and and .
Proof.
By induction on the structure of . ∎
Progress says that if type of component can evolve by exhibiting an I/O action to type , then can eventually (up to carrying out some internal computations) exhibit a corresponding action leading to , and where has type . \threfProgress thus ensures that the behaviours of types can eventually be carried out by the respective components, which entails components are not stuck and allows, together with \threfSubjectReduction, to attest that types faithfully capture component behaviour. Intuitively, our types can be seen as promises of behaviour in the sense that whatever they prescribe as possible behaviours, the components will eventually deliver. For the sake of addressing any possible component configuration, in particular when components have already all the dependencies (internally) satisfied in order to provide some behaviour, it is crucial that types capture the number of (internally) available resources.
5 Concluding Remarks
In this paper we introduce a type language for the choice-free subset of the GC language [6] that characterises the reactive behaviour of components and allows to capture “what components can do”. In particular, our types describe the ability of components to receive and send values, while tracking different kinds of dependencies (per each value and initial ones) and specifying constraints on the boundary of the number of values that a component can emit. We show how types of components can be extracted (inferred) and prove that types faithfully capture component behaviour by means of Subject Reduction and Progress theorems. Typing descriptions such as ours are crucial to promote component reusability, since to use a component we should only need to analyse its type and not its implementation (like in [6]). For instance, for the sake of ensuring the behaviour of a component is compatible with a governing communication protocol, where such compatibility is attested in our case by the conformance of the type to the (local) protocol.
We place our approach in the behavioural types setting (cf. [11]) since our types evolve in order to explain component behaviour (cf. \threfSubjectReduction), in contrast with classic subject reduction results where the type is preserved. In the realm of behavioural types, we distinguish Multiparty Asynchronous Session Types [10] which actually lay the basis for the protocol language of our target model [6]. The model builds on the idea that protocols can be used to directly program the interaction, and not only serve as a specification/verification mechanism, following the approach of choreographic programming [5, 17].
We discuss some closely related work, starting by Open Multiparty Sessions [4] which to some extent shares the same goals and the same background (cf. [10]). The approach in [4] targets the composition of protocols by considering that one of the participants can actually be instantiated by an external environment. Two protocols can then be connected if there is a participant in each that can serve as the interface to the other interaction. So protocols can be viewed as the units of composition instead of components like in our case, and reusing such protocols in other compositions requires compatibility between the I/O actions which are prescribed for the interfacing role. The main difference is therefore that we consider components that are potentially more reusable considering the I/O flexibility provided the reactive flavour.
We also identify the CHOReVOLUTION [12]project where the assembly of services via a choreography is addressed. The I/O flexibility is provided by adapters at assembly time that can solve I/O interface mismatches between service and choreography. We remark that the CHOReVOLUTION approach is at a very mature state (including tool support [3]), where however an assembly of services cannot be provided as a unit of reuse (like our composite components). Differently, our type-based approach aims at abstracting from the implementation and provides more general support for component substitution and reuse. Similar to our work, the model of service based architectures (SOA’s) [14] exposes components that exchange services between each other via ports. However, the authors do not present the type language, where some ideas we presented for extracting a type of a component could be used for the model they present.
We may also find the notion of distributed components is the Signal Calculus (SC) [7], where in each component a process is located. The type-based approach presented in [8] addresses the issue of ensuring SC local processes are composed and interact in a way such that they follow a well-defined protocol of interaction. Our model embeds the protocol in the operational semantics so such coordination is ensured by construction. Our types focus on a different purpose of ensuring data dependencies are met in order to ensure components are not stuck in the sense of the progress result.
In the work about Interactive verification of ADPs [15] the authors introduce a notion of component type which characterizes components with a certain behaviour. One core difference between [15] and our work is that the authors do not provide any means to automatically extract types from given components.
We believe the ideas reported in this paper can contribute to the theoretical basis for providing support for component-based development in distributed systems. Immediate directions for future work include the support for protocols with branching, and providing a characterisation of the substitution principle [13] based in our types. Further challenges remain at the level of conveying the theoretical model to concrete applications, in particular regarding component deployment and the support for their persistent reuse.
Acknolegements
We thank the anonymous reviewers for their suggestions and comments which helped us to improve the paper.
References
- [1]
- [2] Amazon Web Services, Inc (2019): AWS Lambda: Developer Guide. Available at https://docs.aws.amazon.com/en_pv/lambda/latest/dg/lambda-dg.pdf#welcome.
- [3] Marco Autili, Amleto Di Salle, Francesco Gallo, Claudio Pompilio & Massimo Tivoli (2019): CHOReVOLUTION: Automating the Realization of Highly–Collaborative Distributed Applications. In Hanne Riis Nielson & Emilio Tuosto, editors: 21th International Conference on Coordination Languages and Models (COORDINATION), Coordination Models and Languages LNCS-11533, Springer International Publishing, pp. 92–108, 10.1007/978-3-030-22397-7_6. Part 2: Tools (1).
- [4] Franco Barbanera & Mariangiola Dezani-Ciancaglini (2019): Open Multiparty Sessions. In Massimo Bartoletti, Ludovic Henrio, Anastasia Mavridou & Alceste Scalas, editors: Proceedings 12th Interaction and Concurrency Experience, ICE 2019, Copenhagen, Denmark, 20-21 June 2019, EPTCS 304, pp. 77–96, 10.4204/EPTCS.304.6.
- [5] Marco Carbone & Fabrizio Montesi (2013): Deadlock-freedom-by-design: multiparty asynchronous global programming. In Roberto Giacobazzi & Radhia Cousot, editors: The 40th Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL ’13, Rome, Italy - January 23 - 25, 2013, ACM, pp. 263–274, 10.1145/2429069.2429101.
- [6] Marco Carbone, Fabrizio Montesi & Hugo Torres Vieira (2018): Choreographies for Reactive Programming. CoRR abs/1801.08107. Available at http://arxiv.org/abs/1801.08107.
- [7] Gian Luigi Ferrari, Roberto Guanciale & Daniele Strollo (2006): JSCL: A Middleware for Service Coordination. In Elie Najm, Jean-François Pradat-Peyre & Véronique Donzeau-Gouge, editors: Formal Techniques for Networked and Distributed Systems - FORTE 2006, 26th IFIP WG 6.1 International Conference, Paris, France, September 26-29, 2006, Lecture Notes in Computer Science 4229, Springer, pp. 46–60, 10.1007/11888116_4.
- [8] GianLuigi Ferrari, Roberto Guanciale, Daniele Strollo & Emilio Tuosto (2007): Coordination Via Types in an Event-Based Framework. In John Derrick & Jüri Vain, editors: Formal Techniques for Networked and Distributed Systems - FORTE 2007, 27th IFIP WG 6.1 International Conference, Tallinn, Estonia, June 27-29, 2007, Proceedings, Lecture Notes in Computer Science 4574, Springer, pp. 66–80, 10.1007/978-3-540-73196-2_5.
- [9] Nicola Dragoniand Saverio Giallorenzo, Alberto Lluch-Lafuente, Manuel Mazzara, Fabrizio Montesi, Ruslan Mustafin & Larisa Safina (2017): Microservices: Yesterday, today, and tomorrow. In Manuel Mazzara & Bertrand Meyer, editors: Present and Ulterior Software Engineering, Springer, pp. 195––216, 10.1007/978-3-319-67425-412.
- [10] Kohei Honda, Nobuko Yoshida & Marco Carbone (2016): Multiparty Asynchronous Session Types. J. ACM 63(1), pp. 9:1–9:67, 10.1145/2827695.
- [11] Hans Hüttel, Ivan Lanese, Vasco T. Vasconcelos, Luís Caires, Marco Carbone, Pierre-Malo Deniélou, Dimitris Mostrous, Luca Padovani, António Ravara, Emilio Tuosto, Hugo Torres Vieira & Gianluigi Zavattaro (2016): Foundations of Session Types and Behavioural Contracts. ACM Comput. Surv. 49(1), pp. 3:1–3:36, 10.1145/2873052.
- [12] S. Keller, M. Autili & M. Tivoli (2014): CHOReVOLUTION project. http://www.chorevolution.eu.
- [13] Barbara Liskov & Jeannette M. Wing (1994): A Behavioral Notion of Subtyping. ACM Trans. Program. Lang. Syst. 16(6), pp. 1811–1841, 10.1145/197320.197383.
- [14] A. Malkis & D. Marmsoler (2015): A Model of Service-Oriented Architectures. In: 2015 IX Brazilian Symposium on Components, Architectures and Reuse Software (SBCARS), IEEE Computer Society, Los Alamitos, CA, USA, pp. 110–119, 10.1109/SBCARS.2015.22.
- [15] Diego Marmsoler & Habtom Kashay Gidey (2019): Interactive verification of architectural design patterns in FACTum. Formal Aspects of Computing 31(5), pp. 541–610, 10.1007/s00165-019-00488-x. Alessandra Russo, Andy Schuerr, and Heike Wehrheim.
- [16] M. Douglas Mcllroy (1969): Mass Produced Software Components. In: Software Engineering: Report of a conference sponsored by the NATO Science Committee, Garmisch, pp. 138–155.
- [17] Fabrizio Montesi (2013): Choreographic Programming. Ph.D. thesis, IT University of Copenhagen. Available at http://fabriziomontesi.com/files/choreographic_programming.pdf.
- [18] Object Management Group, Inc. (OMG) (2014): Business Process Model and Notation, specification version 2.0.2. Available at https://www.omg.org/spec/BPMN/2.0.2/.
- [19] W3C WS-CDL Working Group (2004): Web Services Choreography Description Language Version 1.0. Available at http://www.w3.org/TR/2004/WD-ws-cdl-10-20040427/.
Appendix A Modified type
Now we introduce the modified type denoted by . The interfacing component of the composite one, beside its interaction with other components, also interacts with an external environment. In this case the crucial part is that it is able to receive in any moment values that are input externally. For the purpose of observing if a type of a component can perform actions required by the protocol, we need to modify the type according to the possible inputs that a (interfacing) component can receive from the external context without any constraints. The modified type of a type , taking into account the list of corresponding list of forwarders, is denoted by . If is the type of the interfacing component, each dependency on the external input ports is per each value dependency and the number of values available is unbounded (assuming that whenever the value is available it is received on the external input ports). The syntax of -type is given in the Table 6. It is similar to a syntax of the types which we have already shown, with the difference in the number of values received, that in the modified type can be unbounded (infinite). Moreover, the rules defining the semantics of modified type are the same as the ones shown for our typing language (Table 7).
-Type syntax
Types | Constraints | Dependencies |
---|---|---|
Kinds of Dependencies | Boundaries | |
-Type semantics
Definition A.1.
defmod
If is a type of interfacing subcomponent of composite component then is the -modified type where:
where | |||
if , where | |||
if | |||
if |
Note that for we have that
since the only component that forwards the values from/to external environment is component .
Appendix B Conformance relation
Definition B.1.
modevolve if exists a (possibly empty) set of typed input ports such that .
Rule [] ensures that a modified type is conformant with the protocol, where it can receive an input of a matching type with a continuation as a protocol , if a modified type can receive a value on port , and assuming that port receives a values of type and the evolved type is conformant with . Similar reasoning is for an output. Rule [] states that a modified type is always conformant with the termination protocol. Finally, we have two rules [] and [] for the recursion. The premise of Rule [] requires that the type associated with the recursion variable by assumption and the type under consideration are related as (\threfmodevolve). Observing the semantics of modified types, the possible difference between types and is that some initial dependencies might be dropped or that the number of values available on some input ports for some outputs might increase. Rule [] states that is conformant with a protocol , provided that the type is conformant with the body of the recursion under the environment extended with assumption .