Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java REST-client for microservices architecture

Java REST-client for microservices architecture

Руслан Гусейнов: выступление на митапе Общества анонимных тестировщиков 28 марта 2019

Zoya Chizhkova

March 28, 2019
Tweet

More Decks by Zoya Chizhkova

Other Decks in Programming

Transcript

  1. 4 Backend Gett N=153 Ruby & Go HTTP Rest API

    (Json) Rabbit MQ " Продуктовые команды (10+) в Тель-Авиве и Москве " У каждой команды 3-10 микросервисов " Компонентные тесты " Интеграционные тесты - монорепа на Java на всех " Нужно использовать другие сервисы 
 µS1 µS3 µSN µS2
  2. 5 Разная структура и инструменты 
 Когда работаешь со своими

    
 микросервисами Когда нужно использовать код, который написала соседняя команда
  3. 6 Что есть? " com.squareup.retrofit2:retrofit -> square.github.io.okhttp3 " io.rest-assured:rest-assured ->

    org.apache.http.client " io.github.openfeign:feign-core -> java.net.HttpURLConnection " И другие решения 

  4. 7 Retrofit/Feign - intro Retrofit turns your HTTP API into

    a Java interface.(с) public interface RetrofitDriverClient { @GET("api/v1/drivers") Call<DriversResponse> getDrivers(); @POST("api/v1/drivers") Call<DriverResponse> createDriver(@Body Driver driver); } public RetrofitDriverClient getClient() { final Retrofit retrofit = new Retrofit.Builder() .baseUrl(HOST).build(); return retrofit .create(RetrofitDriverClient.class); }
  5. 8 Сессии (cookies) Для чего: " Использовать legacy API, которому

    нужны cookies " Автологиниться в браузер минуя этап аутентификации через Web Хочется сразу из коробки, красиво и без костылей
  6. 9 Сессии - retrofit - 1 public class CookieJarIml implements

    CookieJar { final private Map<String, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { this.cookieStore.put(url.host(), cookies); } @Override public List<Cookie> loadForRequest(HttpUrl url) { final List<Cookie> cookies = this.cookieStore.get(url.host()); if (cookies == null) { return Collections.emptyList(); } else { return cookies; } } }
  7. 10 Сессии - retrofit - 2 Создание клиента с поддержкой

    сессий public SimpleRetrofitClient getClient2() { // create OkHttpClient.Build OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder(); // set up impl of CookieJar interface okHttpBuilder.cookieJar(new CookieJarIml()); Retrofit retrofit = new Retrofit.Builder() .client(okHttpBuilder.build()) .baseUrl(HOST).build(); return retrofit.create(SimpleRetrofitClient.class); }
  8. 11 Сессии - feign 1) Сделать свою реализацию интерфейса Client

    с поддержкой cookies 2) Подключить адаптер io.github.openfeign:feign-okhttp и сделать аналогично с retrofit. public final class OkHttpClient implements Client { private final okhttp3.OkHttpClient delegate; public OkHttpClient() { this(new okhttp3.OkHttpClient()); } public OkHttpClient(okhttp3.OkHttpClient delegate) { this.delegate = delegate; } }
  9. 12 Сессии - rest-assured - 1 Тащить за собой cookies

    public void session() { final Map<String, String> cookies = given() .contentType(ContentType.JSON) .body(AUTH_BODY) .post("/auth").then().extract().cookies(); final String authStatus = given() .cookies(cookies) .get("/auth/status") .then() .extract() .body().asString(); assertEquals("success", authStatus); }
  10. 13 Сессии - rest-assured - 2 Создать CookieFilter public void

    session() { CookieFilter cookieFilter = new CookieFilter(); given() .filter(cookieFilter) .contentType(ContentType.JSON) .body(AUTH_BODY) .post("/auth"); final String authStatus = given() .filter(cookieFilter) .get("/auth/status") .then() .extract() .body().asString(); assertEquals("success", authStatus); }
  11. 14 Interceptors Interceptors - механизм, который позволяет отслеживать, изменять или

    повторять вызовы. public class LoggingInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); log.info("Send request at {}", TimeHelper.getTime()); Response response = chain.proceed(request); log.info("Receive response at {}", TimeHelper.getTime()); return response; } }
  12. 16 Interceptors - retry interceptor public class RetryInterceptor implements Interceptor

    { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); int tryCount = 0; HttpResult httpResult = HttpResult.error(new IOException()); while (tryCount < totalAttempts) { httpResult = getResponse(chain, request); tryCount++; if (httpResult.isOk()) { return httpResult.response; } else if (tryCount < totalAttempts) { // don't wait after the last attempt sleep(waitIntervalMillis); } } // we can't return null here if (httpResult.isOk()) { return httpResult.response; } else { return httpResult.throwException(); } } }
  13. 17 Interceptors - env interceptor - 1 public interface DriverClient

    { @GET("api/v1/{env}") List<Driver> getDrivers(@Path("env") String env); @POST("api/v1/{env}") Driver createDriver(@Path("env") String env, @Body Driver driver); // и еще 15 методов } public interface DriverClient2 { @GET("api/v1/ENV") List<Driver> getDrivers(); @POST("api/v1/ENV") Driver createDriver(@Body Driver driver); // и еще 15 методов }
  14. 18 Interceptors - env interceptor - 2 public class EnvInterceptor

    implements Interceptor { private static final String ENV_ESCAPE = "ENV"; private String env; public EnvInterceptor(String env) { this.env = env; } @Override public Response intercept(@NotNull Chain chain) throws IOException { Request request = chain.request(); int envPathIndex = request.url().pathSegments().indexOf(ENV_ESCAPE); if (envPathIndex > -1) { HttpUrl url = request.url().newBuilder().setPathSegment(envPathIndex, env).build(); return chain.proceed(request.newBuilder().url(url).build()); } else return chain.proceed(request); } }
  15. 19 Логирование Feign io.github.openfeign:feign-slf4j final SimpleFiegnClient client = Feign.builder() .logger(new

    Slf4jLogger()) .logLevel(Logger.Level.FULL) .target(SimpleFiegnClient.class, HOST); Rest-assured RequestLoggingFilter requestLoggingFilter = new RequestLoggingFilter(); ResponseLoggingFilter responseLoggingFilter = new ResponseLoggingFilter(); given() .filter(requestLoggingFilter) .filter(responseLoggingFilter) .contentType(ContentType.JSON) .body(AUTH_BODY) .post("/auth");
  16. 20 Логирование Retrofit com.squareup.okhttp3: logging-interceptor OkHttpClient.Builder client = new OkHttpClient.Builder();

    client.addInterceptor(new HttpLoggingInterceptor(log::info) .setLevel(HttpLoggingInterceptor.Level.BODY)); Retrofit retrofit = new Retrofit.Builder() .client(client.build()) .baseUrl(this.baseUrl) .build();
  17. 21 Deserialization/serialization Feign io.github.openfeign:feign-gson io.github.openfeign:feign-jackson @Test public void createDriver() {

    final String driverName = "Ivan"; final Driver driver = new Driver(driverName, "Ivanov"); final FeignDriverClient client = Feign.builder() .decoder(new GsonDecoder()) .encoder(new GsonEncoder()) .target(FeignDriverClient.class, HOST); final DriverResponse driverResponse = client.createDriver(driver); assertEquals(driverName, driverResponse.getDriver().getFirstName()); }
  18. 22 Deserialization/serialization Rest-assured jackson and gson out of the box

    @Test public void createDriver() { final String driverName = "Ivan"; Driver driver = new Driver(driverName, "Ivanov"); final DriverResponse driverResponse = given() .body(driver, ObjectMapperType.GSON) .post("api/v1/drivers") .as(DriverResponse.class, ObjectMapperType.GSON); assertEquals("Ivan", driverResponse.getDriver().getFirstName()); }
  19. 23 Deserialization/serialization Retrofit com.squareup.retrofit2:converter-gson com.squareup.retrofit2:converter-jackson @Test public void createTest() throws

    IOException { Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .baseUrl(HOST) .build(); final String driverName = "Ivan"; final Driver driver = new Driver(driverName, "Ivanov"); final RetrofitDriverClient client = retrofit.create(RetrofitDriverClient.class); final Response<DriverResponse> response = client.createDriver(driver).execute(); assertEquals(driverName, response.body().getDriver().getFirstName()); }
  20. 24 Lombok - 1 public class Driver { @SerializedName("first_name") private

    String firstName; @SerializedName("last_name") private String lastName; private Integer id; public Driver(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
  21. 25 Lombok - 2 @Getter @Setter public class Driver2 {

    @SerializedName("first_name") private String firstName; @SerializedName("last_name") private String lastName; private Integer id; public Driver2(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
  22. 26 Lombok - 3 @Builder @Getter public class Driver3 {

    @Builder.Default @SerializedName("first_name") private String firstName = "Ivan"; @Builder.Default @SerializedName("last_name") private String lastName = "Ivanov"; private Integer id; } final String lastName = "Petrov"; Driver3 driver = Driver3.builder() .lastName(lastName) .build(); assertEquals("Ivan", driver.getFirstName()); assertEquals(lastName, driver.getLastName());
  23. 27 Наследование - Retrofit vs Feing Skeleton содержит набор уже

    готовых стандартных методов public interface FeignDriverClient extends SkeletonClient { // a few methods } Retrofit:
  24. 29 Итоги Retrofit Feign Rest-assured Api Definition + + Cookies

    + + + Interceptors + + + Deserialization + + + Inheritance - + Popular + - +
  25. 30 Как у нас организован код для работы с микросервисом

    Retrofit " Definition Layer - описание всех методы микросервиса " Validation Layer - валидация status_code и content-type, чтобы сразу зафейлить тест в месте, где что-то пошло не так " Functions - десериализация, класс для переиспользования другими командами Definition Layer Validation Layer Functions Layer