Essentially, the annotation can be declared on any interface or abstract class and through the use of the abstract methods will be implemented for you at compile time, greatly simplifying the creation of HTTP clients.
Let’s start with a simple example. Given the following class:
Pet.java
Pet.java
class Pet {
String name
int age
}
Pet.java
class Pet {
var name: String? = null
var age: Int = 0
}
You can define a common interface for saving new Pet
instances:
PetOperations.java
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;
import io.reactivex.Single;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
@Validated
public interface PetOperations {
@Post
Single<Pet> save(@NotBlank String name, @Min(1L) int age);
}
PetOperations.java
PetOperations.java
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import io.reactivex.Single
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
interface PetOperations {
@Post
fun save(@NotBlank name: String, @Min(1L) age: Int): Single<Pet>
}
Note how the interface uses Micronaut’s HTTP annotations which are usable on both the server and client side. Also, as you can see you can use javax.validation
constraints to validate arguments.
Additionally, to use the javax.validation
features you should have the validation
and hibernate-validator
dependencies on your classpath. For example in build.gradle
:
build.gradle
compile "io.micronaut:micronaut-validation"
compile "io.micronaut.configuration:micronaut-hibernate-validator"
On the server-side of Micronaut you can implement the PetOperations
interface:
PetController.java
import io.micronaut.http.annotation.Controller;
import io.reactivex.Single;
@Controller("/pets")
public class PetController implements PetOperations {
@Override
public Single<Pet> save(String name, int age) {
Pet pet = new Pet();
pet.setName(name);
pet.setAge(age);
// save to database or something
return Single.just(pet);
}
}
PetController.java
import io.micronaut.http.annotation.Controller
import io.reactivex.Single
@Controller("/pets")
open class PetController : PetOperations {
val pet = Pet()
pet.name = name
pet.age = age
// save to database or something
return Single.just(pet)
}
You can then define a declarative client in src/test/java
that uses @Client
to automatically, at compile time, implement a client:
PetClient.java
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;
@Client("/pets") (1)
public interface PetClient extends PetOperations { (2)
@Override
Single<Pet> save(String name, int age); (3)
}
PetClient.java
import io.micronaut.http.client.annotation.Client
import io.reactivex.Single
@Client("/pets") (1)
interface PetClient extends PetOperations { (2)
@Override
Single<Pet> save(String name, int age) (3)
}
PetClient.java
Once you have defined a client you can simply @Inject
it wherever you may need it.
Recall that the value of @Client
can be:
An absolute URI. Example
[https://api.twitter.com/1.1](https://api.twitter.com/1.1)
A relative URI, in which case the server targeted will be the current server (useful for testing)
A service identifier. See the section on Service Discovery for more information on this topic.
In a production deployment you would typically use a service ID and to discover services automatically.
Another important thing to notice regarding the save
method in the example above is that is returns a Single type.
This is a non-blocking reactive type and typically you want your HTTP clients not to block. There are cases where you may want to write an HTTP client that does block (such as in unit test cases), but this are rare.
The following table illustrates common return types usable with :
Returning CompletableFuture instances is also supported. Note that returning any other type will result in a blocking request and is not recommended other than for testing.