Managing data sources

Working with HTTP sources

See also: OpenAPI

Orbital has first-class support for working with OpenAPI specs and SOAP WSDLs The following guide is intended for consuming HTTP services without a separate spec.

Describing HTTP with Taxi

If you’re not working with other HTTP specs, you can describe your services directly using Taxi. Taxi is very rich and expressive, and is designed to fully support describing API sources, for teams who are not currently using an existing standard.

Services

A service is simply a group of operations.

service PeopleService {
   operation listAllPeople():Person[]
}

Services that contain HTTP operations may optionally declare an @HttpService annotation, which allows it to set a baseUrl for all contained operations.

@HttpService(baseUrl="https://foo.com")
service PeopleService {
   // resolves to https://foo.com/people
   @HttpOperation(method = 'GET', url = '/people')
   operation listAllPeople():Person[]
}

Http Operations

An operation defines a function on the API.

@HttpOperation(method = 'GET', url = 'https://myservice/people')
operation listAllPeople():Person[]

Operations often have annotations that provide hints to tooling as to how to invoke them.

Taxi ships with some standard annotations, as part of it’s std-lib. Although it’s up to individual tooling to determine how to interpret these, the suggested usage is as follows:

AnnotationUsage
@HttpOperation(method,url)Indicates that the operation should be invoked over HTTP, using the provided method and url
@HttpRequestBodyIndicates that a parameter will be found on the request body
@PathVariable(name)Indicates a property from the path that should be populated with a variable passed to the operation
@HttpHeader(name,value, prefix)Defines how header are passed. See below

Http Headers

Http headers are passed as annotations, which follow the following format:

annotation HttpHeader {
   name : String
   [[ Pass a value when using as an annotation on an operation. For parameters, it's valid to allow the value to be populated from the parameter. ]]
   value : String?
   prefix : String?
   suffix : String?
}

Http Headers can be defined in operations in a few different ways:

As a fixed value, on an operation

service MyService {
  @HttpOperation(method = "GET", url = "...")
  @HttpHeader(name = "Consumes", value = "application/json")
  operation findPeople():Person[]
}

As an input into an operation

service MyService {
  operation findPeople(
     @HttpHeader(name = "Cache-Control", prefix = "max-age=") cacheMaxAge : CacheMaxAge
  ):Person[]
}

Which is then invoked as:

given { maxAge : CacheMaxAge = 64000 } 
find { Person[] }

Additionally, header values can be calculated, using expressions:

service MyService {
  operation findPeople(
     @HttpHeader(name = "If-Modified-Since") ifModifiedSince : Instant = addDays(now(),-7)
  ):Person[]
}

Examples

service MovieService {
   @HttpOperation(method = "GET" , url = "https://myMovies/movies")
   operation findAllMovies() : Movie[]

   @HttpOperation(method = "GET" , url = "https://myMovies/movies/{id}")
   operationFindMovie( @PathVariable(name = "id") id : MovieId ) : Movie
}

@HttpService(baseUrl = "http://actorService")
service ActorService {
   // resolves to http://actorService/actors
   @HttpOperation(method = "GET" , url = "/actors")
   operation findAllActors() : Actor[]
}

Names of operation parameters are optional. This is to encourage developers to leverage a richer type system where possible:

// These two declarations are both valid, and describe the same operation
operation convertUnits(source:Weight, targetUnit:Unit):Weight
operation convertUnits(Weight,Unit):Weight

Http Parameters

Operation Contracts & Constraints

Contracts and constraints are useful for telling tooling about what functionality an operation can provide, and what conditions must be met before invoking.

Both contracts and constraints use the same syntax.

type Money {
   currency : String
   amount : Decimal
}
operation convertCurrency(input: Money,
      targetCurrency: String) : Money(from input, currency = targetCurrency)

from input

A contract may indicate that a return type is derived from one of the inputs, by using the from {input} syntax:

operation convertUnits(input: Weight, target: Unit):Weight( from input )

Attribute constraints

Attribute constraints describe either a pre-condition (if on an input) or a post-condition (if on a return type) for an operation

operation convertFromPounds(input : Money(currency = 'GBP'), target: Currency)
    : Money( from input, currency = target)

As shown above, attribute constraints may either be:

  • A constant value (ie., "GBP")
  • A reference to an attribute of another parameter.
  • Nested syntax is supported (ie., foo.bar.baz)

These constraints are applicable on types too - see

type constraints
for an example.

Retry policies

When integrating with external REST APIs, handling intermittent failures such as HTTP 500 errors is crucial for maintaining application reliability.

Taxi allows defining retry policies, which Orbital will honour when things go wrong

Fixed Delay Retry Policy

A fixed delay retry policy retries operations with a constant wait time between attempts. This is useful for temporary issues on the external service’s end.

For example, to retry the getReviews operation upon receiving an HTTP 500 error, with a 5-second wait for up to 10 attempts, define the service as follows:

service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500], fixedRetryPolicy = @HttpFixedRetryPolicy(maxRetries = 10, retryDelay = 5))
    operation getReviews(id: FilmId): FilmReview[]
}

To include additional error codes like HTTP 502, simply adjust the responseCode attribute:

service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500, 502], fixedRetryPolicy = @HttpFixedRetryPolicy(maxRetries = 10, retryDelay = 5))
    operation getReviews(id: FilmId): FilmReview[]
}

Exponential Delay Retry Policy

An exponential delay retry policy increases the delay between retries, which can be further randomized using jitter to avoid synchronized retry storms:

service ReviewsApi {
    @HttpOperation(method = "GET", url = "https://reviews/{id}")
    @HttpRetry(responseCode = [500, 502], fixedRetryPolicy = @HttpExponentialRetryPolicy(maxRetries = 10, retryDelay = 5, jitter = 0.5))
    operation getReviews(id: FilmId): FilmReview[]
}

With the above definition, Orbital will try invoking the Rest at most 10 times in case of Http 500 or Http 502 errors.

However, the delay between retries will increase exponentially.

With a jitter value between 0 and 1, you add some randomness to the delay time by introducing a random delay, or “jitter”, to the next retry delay time. This ensures that the retries are not synchronous and reduces the likelihood of a retry storm.

Here’s how the table would look with exponential backoff with Jitter:

Retry AttemptDelay Time (seconds)Jitter Range (seconds)Actual Delay Time (seconds)
11.00.51-0 - 1.5
22.00.51-5 - 2.5
34.00.53.5 – 4.5
48.00.57.5 – 8.5
516.00.515.5 – 16.5
Previous
Using SOAP
Next
Using Protobuf