Tailored for microservices and APIs
Jolie is a contract-first programming language, which puts API design at the forefront. It supports both synchronous and asynchronous communication. Data models are defined by types that support refinement (in red on the right), and DTO (Data Transfer Objects) transformations are transparently managed by the interpreter.
type GetProfileRequestType { id:int } type GetProfileResponseType { name:string surname:string email:string( regex(".*@.*\\..*") ) accounts[0,*] { nickname:string service_url:string enabled:bool } ranking:int( ranges( [1,5] ) ) } type SendMessageRequestType { id:int message:string( length( [0,250] ) ) } interface ProfileInterface { requestResponse: // Synchronous RPC getProfile( GetProfileRequestType )( GetProfileResponseType ) oneWay: // Asynchronous sendMessage( SendMessageRequestType ) }
Structured workflows
Jolie comes with native primitives for structuring workflows, for example in sequences (one after the
other) or parallels (go at the same time). This makes the code follow naturally from the requirements,
avoiding error-prone bookkeeping variables for checking what happened so far in a computation. For
example, the following code says that the operations publish and edit become
available at the same time (|), but only after (;) operation
login is invoked:
login( credentials )() { checkCredentials };
{ publish( x ) | edit( y ) }
Dynamic error handling for parallel code
Programming reliable parallel code is challenging because faults may cause side-effects in parallel
activities. Jolie comes with a solid semantics for parallel fault handling. Programmers can update the
behaviour of fault handlers at runtime, following the execution of activities thanks to the
install primitive.
from console import Console from time import Time service ErrorHandling { embed Console as Console embed Time as Time main { scope( grandFather ) { install( this => println@Console( "recovering grandFather" )() ); scope( father ) { install( this => println@Console( "recovering father" )() ); scope ( son ) { install( this => println@Console( "recovering son" )() ); sleep@Time( 500 )(); println@Console( "Son's code block" )() } } } | throw( a_fault ) } }
Everything you build can be used to build again
Jolie offers many ways for building complex software from simple services. Even the deployment architecture of a system can be programmed with native primitives, generalising common practices. Whatever you build, is again a service that you can expose; so, it can be reused to build again! Here are some examples of composition:
Orchestration: an orchestrator is a service that offers functionalities obtained by coordinating other services with a workflow.
Aggregation: a generalisation of proxies and load balancers, which you can use to compose and expose the APIs of separate services
Redirection: a generalisation of virtual servers, which hides the actual locations of services to clients by assigning logical names to services.
Embedding: a generalisation of application servers, which runs other services as inner components. It enables fast local communications and can even run code written in different languages than Jolie, such as Java and Javascript (with more coming)!