Describing services
Using our taxonomy to describe rich semantic services
Services
A service is simply a group of operations.
service PeopleService {
operation listAllPeople():Person[]
}
Operation
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:
Annotation | Usage |
---|---|
@HttpOperation(method,url) | Indicates that the operation should be invoked over HTTP, using the provided method and url |
@HttpRequestBody | Indicates that a parameter will be found on the request body |
@ServiceDiscoveryClient(serviceName) | Indicates that the service's absolute url should be discovered from Service Discovery, using the provided name for lookup |
@PathVariable(name) | Indicates a property from the path that should be populated with a variable passed to the operation |
Examples
service MovieService {
@HttpOperation(method = "GET" , url = "https://myMovices/movies")
operation findAllMovies() : Movie[]
@HttpOperation(method = "GET" , url = "https://myMovices/movies/{id}")
operationFindMovie( @PathVariable(name = "id") id : MovieId ) : Movie
}
// Using a service discovery client, such as Eureka
@ServiceDiscoveryClient(serviceName = "actors")
service ActorService {
@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 desribe 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
)
- Nested syntax is supported (ie.,
These constraints are applicable on types too - see type constraints for an example.
Query operations
RESTful style API's are great for discovering resources that have fairly predictable resource paths - such as /Author/jkrowling/Books
, returns all the books that jkrowling
has authored.
However, once you get beyond simple resource lookup, and into more bespoke query-style operations, services tend to expose their own query APIs. Taxi supports this with a rich, extensible syntax for exposing query contracts.
Here's an example:
taxiQl query personQuery(@RequestBody body:VyneQlQuery):Person[] with capabilities {
filter(=,in,like),
sum,
count
}
This describes:
- A
query
endpoint that understands a query language calledtaxiQl
- An operation named
personQuery
, which accepts aVyneQlQuery
- The operation understands how to perform filtering using
=
,in
andlike
semantics - And supports custom aggregation functions called
sum
andcount
.
Grammar names
In the above example, we saw a query endpoint that understands a query language called taxiQl
.
From taxi's perspective, the name of the grammar being used is entirely arbitary. The query server that is invoking endpoints must understand how to construct queries in the described language.
The Vyne query server understands how to construct taxiQl queries, with support for additional grammars (such as SQL
, and GraphQL
) planned.
However, query servers may implement their own grammar support.
Streaming data
Data sources that expose a stream of data (such as a websocket, or a message queue) can expose this using the Stream<>
type:
service PriceService {
operation getPrices(): Stream<Price>
}