Orcha is a Proto-language in the area of Information Technology i.e. a first language of a language family common to human beings and machines.

For Orcha, the language family is that one of human activities. So Orcha (ORChestration of Human Activities) is a first language of the family of human activities common to human beings and machines.

To be used by humans, not only Information Technology specialists, but for everybody, Orcha has:

  • Instructions describing daily life activities
  • Almost no learning time
  • Out of the box use with a simple tooling

To be a first language of the family of human activities:

To be used by machines :

  • Orcha programs have a few instructions (compute, receive, from, send, to, condition, when)
  • Orcha programs are represented by simple abstract syntax trees easy to parse by programs
  • All terms added to the instructions set that enhance the vocabulary of Orcha are fully specified by a simple language easy to interpret by programs (the Orcha configuration language)

Real world / virtual world interactions

Orcha is event/message driven

Orcha interacts with the real world or the virtual world through events. An event can come from a sensor of the Internet of Things, a smartphone, when an email is received, when a file is put into a directory, and so on.

Waiting for an event in Orcha is simple. Simply writes:

receive event from source

event is a variable containing the event received from source. There is no need to declare event before using it in a receive instruction. Source is defined by the user or programmatically using the Orcha configuration language: at this step, humans or programs select the kind of event source used (databases, files…).

Send an event is also easy. Simply write:

send event to destination

Here again event is a variable and destination must be configured.

Activities are triggered by events

Activities are triggered by events using a compute instruction:

compute makeSomething with event.value

Where event.value is the content of the received event.

A basic configuration can remove the compute from the above instruction, so you should simply write:

makeSomething with event.value

Activities from the real world are services (applications) in the virtual world. Orcha is programming language agnostic and this is at configuration step that the services are selected by the user or programmatically.

Waiting for the completion of an activity

To reflect the real world where activities can be done in parallel, Orcha computes activities asynchronously: all computes are processed in parallel and synchronization points (done by when instructions) wait for the completion of compute instructions:

when “makeSomething terminates”

Orcha is business oriented

Putting all together we can write our first Orcha program:

receive event from source
makeSomething with event.value
when “makeSomething terminates”
send makeSomething.result to destination

For a realistic program, source, makeSomething and destination must be replaced by terms making sense in a business/domain. For instance:

receive luggage from passenger
scanLuggage with luggage.content
when “scanLuggage terminates”
send scanLuggage.result to authority

Apart the instructions set (compute, receive, from, send, to, condition, when), every terms in an Orcha program, are defined by the user (or programmatically) and should be domain/business oriented.

In the instructions below, advertising, advertiser and recognitionAlgorithm are defined by the user:

receive advertising from advertiser receive recognitionAlgorithm from security

The content of an event is accessible using dot value:

compute someThing with advertising.value

In the case of a structured event, it is also possible to go deeper into this event. For example:

compute someThing with advertising.value.author.name

Or simply:

compute someThing with advertising.author.name

Where advertising contains the Json structure:

{“author”:
{“name”:”...”}
}

Specifications of the Orcha language

Instructions set

The Orcha language defines a minimal set of instructions for the description of any human activities. In that sense, Orcha is a first language. As a Proto-language in the area of Information Technology, Orcha language is also common to human beings and machines.

The instructions set is composed of:

Instruction Description
receive <event> from <source event handler> [condition <expression>] asynchronous waiting for an event triggering an activity. The event comes from the real world (a device for instance), an external service (an application) or is an event sent within an Orcha program by a send instruction. from is the source of the event. condition filters the events to be received according to the Orcha expression language
compute <activity> [with <event>.value [,<event>.value, ...]] processing of an activity (asynchronous service call i.e. application call). with (optional): information or data needed to process an activity (the arguments list separated by commas)
when "Orcha expression" waits for the completion of an activity (asynchronous synchronization point for events and/or services (see Orcha expression language))
send <event>.result to <destination event handler> sends an event to the real world (a device), an external services or an Orcha program.to : the destination of an event to be sent

The Orcha expression language

Orcha uses an expression language in when clauses:

when "(<codeToBenchmark1> terminates|fails condition <expression>) [logicalOperator (...)]"

Logical operators:

  • and
  • or
  • not

expression is written in Spring Expression Language

Expression are also used for filtering events in a receive instruction.

Examples:

when "(codeToBenchmark1 terminates condition == -1) and (codeToBenchmark2 terminates condition == 1)"
receive event from orchaProgramSource condition "(specification == 'TV' or order.price <= 30) and order.account==true)"

Events can contain applications

While events bring information or data to an Orcha program, it can also describes a service. Consider the following example where recognitionAlgorithm is a service:

receive recognitionAlgorithm from security
compute faceRecognition with recognitionAlgorithm.value

Dispatching events

Dispatch the same event to many computes or sends is easy. Simply use any number of receive instructions as needed (two in the following example):

receive event from source
compute something with event.value

receive event from source
send event.value to destination

In Orcha there is no switch… case instruction because there is no need of an instruction rarely used if there are simplest and equivalent instructions.

To dispatch events filtered by a condition (see …) different variables must be used because finally the events are different since the conditions are different.

receive event1 from source condition ...
compute something with event1.value

receive event2 from source condition ...
send event2.value to destination

It is not possible to group many instructions in a block and trigger them together when an event occurs. For instance, it is not possible to write two computes when a single event occurs. You should use a subprogram instead : conceptually, a group of instructions is a sub activity (see subprogram).

Broadcasting of messages, synchronisation on several events and transmission of several events to a service

It is always possible to combine several events in a when instruction. In the following example, the when instruction waits for two events:

when "(codeToBenchmark2 terminates condition == 1) and (codeToBenchmark1 terminates condition == -1)"

The following compute instruction can use any events:

compute X with codeToBenchmark2.value, codeToBenchmark1.value

or

compute X with codeToBenchmark2.value

If several events are sent to a compute, they are received in a Javascript array, a Java List... (depending on the language used to implement the service) ordered according to the ORDER OF THE EVENTS IN THE WHEN INSTRUCTION. For example, whatever the order of the events in the compute instruction:

compute selectTaxi with selectHotel.value, selectTrain.value

or

compute selectTaxi with selectTrain.value, selectHotel.value

They will be received in the order defined in the when instruction:

when "selectHotel terminates and selectTrain terminates"

selectHotel will the the first element of the received array, selectTrain the second.

Why? Because several events used in a when requires a resequencing of messages, and a different order in the compute would imply another resequencing which is time consuming.

Architectural consideration: the more a service requires events as arguments, the more it is coupled with services that sends those events. But one of the main advantage of architecture driven by events, advocate by Orcha, is the low coupling. It increases the reusability of services, a better dedugging...

The result of a compute can be broadcasted to many when instructions. In the following example, the result of compute selecTrain is sent to two when instruction:

compute selectTrain with travelInfo.value

when "selectTrain terminates"
...
when "selectHotel terminates and selectTrain terminates"

Events conversion of format

Par défaut Orcha peut lire (avec une instruction receive) et lire (avec une instruction send) du Json provenant de fichiers ou de bases de données qui supportent Json comme MongoDb. Dans ce cas, un service écrit en Javascript pourra manipuler directement le Json, un service écrit en Java aura recours automatiquement au convertisseur Jackson (https://github.com/FasterXML/Jackson). Il suffira dans ce cas de préciser que le type requis est le type Mime application/json dans la configuration du programme Orcha pour que la traduction Json <-> Javascript ou Json <-> Java se fasse automatiquement. Cependant, si un service reçoit en argument un événement d’un type différent de celui qu’il attend, Orcha va chercher automatiquement si un convertisseur de format existe. L’exemple suivant est celui d’un service recevant un événement qui utilise une date au format jj/mm/aaaa : alors que la date est reçue sous forme d’une chaîne de caractères : Dans ce cas, Orcha va chercher si un convertisseur existe, et l'appeler automatiquement. Un convertisseur est une classe Java (écrite en Groovy dans l'exemple ci-dessous) qui est annotée avec, et qui dispose d'une méthode appelée convert recevant en argument le type à convertir et retournant un résultat du type converti.

Dynamically addition of activities

Once an activity has been defined with the Orcha language, and the related activity is in process (the Orcha program is running), it is still possible to enhance the activity by the addition of others activities with no others instruction but the existing Orcha instructions set.

An activity to be added is described as usual by Orcha instructions beginning by a receive:

receive event from orchaEventHandler

where orchaEventHandler is a publication/subscription source. This source is always present out of the box in any Orcha program without any configuration. Such a source has any number of subscribers (many Orcha programs each beginning with the above instruction).

On the other side, the side of the existing activity, there is, as usual, send instructions like:

send event to x

Actually, each send instruction sends an event to two destinations: the given destination (x in the previous example) and the orchaEventHandler. So there is nothing to add but only the use of send instructions (as usual) to activate the orchaEventHandler.

The instruction

receive event from orchaEventHandler

receives all the events from all the send instructions. In order to select the events to be received, which is often useful, a condition can be added to the receive instruction:

receive event from orchaEventHandler condition event.price < 1000

Events are filtered so only the selected events will be processed.

Notice that the above receive instruction receives and filters the local events (events sent by any send instruction from all the running Orcha programs). But this publication/subscription mechanism can be used remotely using a message broker. Simply use the instruction

send event to x

Where x must be configured to send the event to a message broker. On the remote side, orchaEventHandler (which is only available for a local use) must be replaced by

receive event from y condition ...

Where y is configured to use the message broker.

Orcha subprograms

When an activity is composed of several activities, subprograms can be used: one per sub activity.

Consider the following main program where a compute is used to call a subprogram:

compute subProgram with something

subProgram can be coded in Orcha. The following example has two receive instructions so the input event will be dispatched:

receive event from mainProgram


receive event from mainProgram

Unlike procedural programming where subprograms are used to improve the readability, Orcha subprograms are used to:

  • Improve performance and reliability because subprograms are loosely coupled components that can be processed in different processes or machines
  • Model high level business/domain activities in main program hiding low level details in subprograms

Orcha programs and subprograms use adapters to communicate. Many adapters can be used such as files for testing or debugging purpose.

The next Orcha program:

receive order from bankCustomer

can use the following file adapter:

@Bean
EventHandler bankCustomer(){
EventHandler eventHandler = new EventHandler(name: "bankCustomer")
def inputFileAdapter = new InputFileAdapter(directory: 'data/input', filenamePattern: "bankOrder.json")
eventHandler.input = new Input(mimeType: "application/json", type: "service.orchaPartitioning.Order", adapter: inputFileAdapter)
return eventHandler
}

Messaging middlewares

Nevertheless a messaging middleware is a best solution for communication between Orcha programs since it is reliable and secured. Many communication modes are also available using middlewares: one to one, one to many and many to many. Orcha communication modes follow a publish-subscribe model, where data is broadcast through shared topics. The publish-subscribe communication model lets new applications be added to the topology without disruption of the existing flow.

In order to replace the file adapter with a messaging middleware one, just use the following configuration:

@Bean
EventHandler bankCustomer(){
def eventHandler = new EventHandler(name: "bankCustomer")
def middlewareAdapter = new MessagingMiddlewareAdapter()
eventHandler.input = new Input(mimeType: "application/json", type: "service.orchaPartitioning.Order", adapter: middlewareAdapter)
return eventHandler
}

Message partitioning

Furthermore partitioning of messages is also feasible. Partitioning means routing a message to different Orcha programs according to the message content, or properties. It ensure that data identified by common characteristics are processed by the same Orcha program. The following example, where two Orcha programs are called (processOrderBank1 and processOrderBank2), uses two partitions.

receive order from bankCustomer condition "bank == 'BANK1'"
compute processOrderBank1 with order.value
...
receive order from bankCustomer condition "bank == 'BANK2'"
compute processOrderBank2 with order.value

Orcha is configured out of the box with RabbitMQ, but Apache Kafka can be used. Just install RabitMQ, start it, and it will be available as soon as an Orcha program is configured with the messaging middleware adapter.

Consumer groups

It is also possible to scale up by creating multiple instances of a same Orcha subprogram. When doing so, different instances of an application are placed in a competing consumer relationship, where only one of the instances is expected to handle a given message.

Combination of Orcha subprograms and local services

You can combine Orcha subprograms with local services written in Java (or Java variation such as Groovy) or Javascript. Consider the following Orcha program:

receive order from bankCustomer condition "bank == 'BANK1'"
compute processOrderBank1 with order.value
...
receive order from bankCustomer condition "bank == 'BANK2'"
compute processOrderBank2 with order.value
...
receive order from bankCustomer condition "bank == 'BANK3'"
compute processOrderBank3 with order.value

The two first computes can be Orcha programs while the last one can be a local service. Here is an example of a Javascript configuration for processOrderBank3:

@Bean
Application processOrderBank3(){
def jsApp = new Application(name: "processOrderBank3", language: "js"")
def scriptAdapter = new ScriptServiceAdapter(file: 'file:src/main/service/processOrderBank3.js')
jsApp.input = new Input(type: "service.orchaPartitioning.Order", adapter: scriptAdapter)
return jsApp
}

Microservices with HTTP

Each Orcha program is a microservice.

With Orcha it is very easy to build a microservice gateway. Consider again the Orcha program:

receive order from bankCustomer condition "bank == 'BANK1'"
compute processOrderBank1 with order.value
...
receive order from bankCustomer condition "bank == 'BANK2'"
compute processOrderBank2 with order.value
...
receive order from bankCustomer condition "bank == 'BANK3'"
compute processOrderBank3 with order.value

This program can receive the input event via a Rest Web Service. In that case the configuration for the bankCustomer event can be:

@Bean
EventHandler bankCustomer(){
EventHandler eventHandler = new EventHandler(name: "bankCustomer")
def httpAdapter = new HttpAdapter(url: '/remoteCustomerOverHttp', method: HttpAdapter.Method.POST)
eventHandler.input = new Input(mimeType: "application/json", type: "service.orchaPartitioning.Order", adapter: httpAdapter)
return eventHandler
}

Auto persistence of messages

Without any change in an Orcha program all the messages (input events, output events, input of services, output of services) can be persist. Orcha comes out of the box with three kind of message stores:

  • MondoDB
  • Redis
  • SQL Databases

Store an input argument as instance of a service in MongoDB can be declared by configuration:

@EventSourcing(messageStore=MessageStore.mongoDB, joinPoint=JoinPoint.before, eventName="order received")
class BankingApplication extends Application{
}

Where the class BankingApplication is used in a service configuration:

@Bean
Application processOrderBank1(){
def application = new BankingApplication(name: "processOrderBank1", language: "...")
...
}

Persistence is convenient for logging, traceability and it leaves the Orcha send instruction for a business purpose like sending a result to another service via a messaging middleware for instance.

The Orcha configuration language

The Orcha configuration language enables the definition of domain/business terms added to an Orcha program. In the following program, source, something and destination must be configured:

receive event from source
compute something with event.value
when “something terminates”
send something.result to destination

The Orcha configuration language has been designed to be written by humans or generated programmatically. Configurations are written in Groovy/Java but JSon can be used.

At the beginning of a project the configuration should be as simple as possible in order to verify easily that the Orcha program and the corresponding business/domain activities match. Perhaps flat files are enough as data sources and certainly basic versions of applications can be used. Here, no advanced skills are necessary to write such a configuration. But in order to defines a configuration ready for production, an operational is needed because he/she knows which and where are the data sources, the applications to be integrated, and so one. Applications are written by developers. Hence, Orcha brings the business/domain to DevOps methods,and transforms a DevOps project into a BusDevOps project.

An Orcha program can get used in several environments. For instance, an event coming from a smartphone can comes from an Email when the same Orcha program is used in two different environments, or a service called by a compute may change according to the environment. In such a case, configurations can be generated automatically by computers.

The Orcha configuration language is divided into two parts:

  • The configuration of service calls (an application call) with the compute instruction
  • The configuration of the event sources or destination sources for the receive and send instructions

For a service call, the configuration defines:

  • The location of the service. Is it a local or a remote service (a web service for instance)?
  • The protocol to access the service (http for instance)
  • What are the argument types of the service?
  • What is the returned type?
  • Optionally the programming language of the service

Here is an example of a configuration for a service running on the local machine written in the JavaScript language:

Application vendor1(){
def jsApp = new Application(name: "vendor1", language: "js")
def scriptAdapter = new ScriptServiceAdapter(file: 'file:orcha/service/order/vendor1.js')
jsApp.input = new Input(type: "service.order.SpecificOrder", adapter: scriptAdapter)
jsApp.output = new Output(type: "service.order.Bill", adapter: scriptAdapter)
return jsApp
}

For an event source or a destination source, the configuration defines:

  • The kind of sources or destinations. Is it a SQL database, a NoSQL database, a file, an Email…?
  • The data format for the content of the event
  • How and when events are triggered (the frequency at which files are read for example)?

Here is an example of a configuration where the files of a directory are used as source of events:

EventHandler customer(){
def eventHandler = new EventHandler(name: "customer")
def fileAdapter = new InputFileAdapter(directory: 'C:/Users/BenC', filenamePattern: "*.json")
eventHandler.input = new Input(mimeType: "application/json", type: "service.order.Order", adapter: fileAdapter)
return eventHandler
}

The reference implementation of Orcha relies on an integration framework offering many configuration options. Here are the current list of these options:

  • AMQP
  • Feed
  • File
  • FTP(S)
  • Gemfire
  • HTTP
  • JDBC
  • JMS
  • JPA
  • Mail
  • MongoDB
  • Redis
  • SFTP
  • Syslog
  • TCP
  • Twitter
  • UDP
  • Web Services
  • Web Sockets
  • XMPP

However, the actual version of Orcha offers a few of these options, but it has been designed to facilitate the integration of the other options.

Orcha tooling

Java must be installed to create Orcha programs. The recommended version is Java Development Kit version 8 (link). Additional softwares may be installed depending on the configuration of Orcha programs: a message broker like ZeroMQ, a NoSQL database like MongoDB for instance.

Gradle must also be installed. Gradle is an open source project available on any platform (Windows, Mac, Linux): see the installation procedure. Gradle is used to build an Orcha project from scratch. This tool is also used to generate executable programs. Executable programs are generated from Orcha programs with their configurations. Executable Orcha programs drive activities exactly as they have been described by the Orcha language: they will wait for events when receive instructions are computed, they will launch services when compute instructions are computed, and so on.

From a service point of view, each Orcha executable program makes orchestration of services. Many compose programs (many orchestrators) interact with each other. Hence the architectural style of Orcha applications is the Microservice style, where the design of the architecture is business/domain driven.

All the stakeholders of an Orcha project (business analysts, operations and developers) can use the same tool to write Orcha programs, their configurations and the associated services: an Integrated Development Environment (IDE). An IDE supporting the Groovy language must be chosen since the configuration language for Orcha programs is Groovy. Many open source IDE can be used like Eclipse.

It is recommended to share a single Orcha project with all the stakeholders during the life cycle of the project. Usual practices in software development used in a DevOps approach are recommended:

  • Share the Orcha program, its different configurations and the associated services in a repository of code and use revision control systems like github to archive the different versions

The principle of revision control systems like Git is: a server centralizes all the programs and their configurations with all the histories of modifications. Then each stakeholder gets on his own machine a copy of the project or only the part of the project he/she is interested in.

See the how-to tutorial to discover how to use these tools.

Advanced practices from the DevOps approach may also be used for complex projects:

  • Use the same environment for testing and production
  • Automate testing
  • ...

These tool will allow you to generate executable programs. Executable Orcha programs are:

  • Portable: Java must be installed to run an Orcha program.
  • Lightweight: the weight of an Orcha program is few tens of Mo
  • Spring Boot programs: the de facto open source standard for enterprise application in Java