15 Mart 2023 Çarşamba

WireMock Alternatifi

Giriş
3 taraf API'leri test etmek için WireMock kullanmak zorunda değiliz. 
1. Test kodunda 3 taraf API'yi taklit eden ama localhost üzerinde çalışan kod yazılır
2. RestTemplate localhost'a yönlendirilir

Örnek
Elimizde şöyle bir kod olsun
// application.properties
downstream.basepath=https://dev.faas.neuw.io/function

//  SpringBootApplication.java
@Bean
public RestTemplate restTemplate(@Value("${downstream.basepath}") String rootUri) {
  return new RestTemplateBuilder().rootUri(rootUri).build();
}

@RestController
public class UpstreamController {

  private final DownstreamClientService downstreamClientService;
 
  @GetMapping("/v1/upstream")
  public Pong test() {
    return downstreamClientService.getPong();
  }
}

@Service
public class DownstreamClientService {
    private final RestTemplate restTemplate;

   public Pong getPong() {
      return restTemplate.getForObject("/ping", Pong.class);
  }
}
test içinde şöyle yaparız. 
Test yine UpstreamController nesnesin tetikler. 
O da DownstreamClientService nesnesini tetikler. 
O da RestTemplate localhost'u işaret ettiği için localhost üzerindeki ping servisini tetikler
// src/test/resources/application.properties
# on this port the unit tests will run
server.port=58080
downstream.basepath=http://localhost:${server.port}

//SpringBootApplicationTests.java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class SpringBootApplicationTests {

  @Autowired
  TestRestTemplate testRestTemplate;

  @Test
  void testPong() {
    Pong res = testRestTemplate.getForObject("/v1/upstream", Pong.class);
    assertEquals(true, res.isSuccess());
    assertEquals("pong", res.getMessage());
  }
}

@RestController
public class DownstreamMockController {

  @GetMapping("ping")
  public Pong test() {
    logger.info("for testing only, downstream hosted from the test package's controller");
    return new Pong("pong", true, new Date().getTime(), true);
  }
}


14 Mart 2023 Salı

SpringBoot Actuator - Metrics Endpoint Open Telemetry Exporter

Giriş
Açıklaması şöyle
Until Spring Boot 2, Telemetry traces integration was made using Spring Cloud Sleuth. For Spring Boot 3 those features were migrated to Micrometer. Micrometer handles the instrumentation of the application, integrating nicely with Spring Boot and other libraries you are probably using. But Micrometer itself doesn’t export the traces to the remote Open Telemetry endpoint. For doing that we need an additional dependency on the Open Telemetry Exporter.
Maven
Şöyle yaparız
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
SpanExporter Sınıfı
Açıklaması şöyle
The Open Telemetry Exporter will try to export traces to a monitoring tool running locally, but it also provides a class to export them to a remote endpoint. This integration is currently not natively available in Spring Boot 3 (at least I didn’t find it) so you have to register a bean that will handle this task
Örnek
Şöyle yaparız
@Configuration
public class OtelConfiguration {

  @Bean
  SpanExporter otlpHttpSpanExporter() {
    return OtlpHttpSpanExporter
      .builder()
      .addHeader("Content-Type", "application/x-protobuf")
      .setEndpoint("http://remote-monitoring-tool/enpoint/v1/trace")
      .build();
  }
}




9 Mart 2023 Perşembe

SpringMVC RestTemplate.postForObject metodu

Giriş
Http Post ile belirtilen sınıfı gönderir. Üçüncü parametre sonucun tipini belirtir.
İmzası şöyle
// Without parameters
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)

// Parameter object list as var args
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, 
  Object... uriVariables)

// Parameter object list as a map
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, 
  Map<String, ?> uriVariables)
postForObject metodu - Parametresiz
Örnek

Şöyle yaparız.
String baseUrl = serviceSettings.getUrl();
String result = restTemplate.postForObject(baseUrl, foo, String.class);
Örnek
Şöyle yaparız
Todo todoObject = ...;

Todo todoCreated = restTemplate
  .postForObject("https://jsonplaceholder.typicode.com/todos", todoObject, Todo.class);

// Or postForEntity
ResponseEntity<Todo> todoResponse = restTemplate
  .postForEntity("https://jsonplaceholder.typicode.com/todos", todoObject, Todo.class);
Todo todoInserted = todoResponse.getBody();
System.out.println(todoResponse.getStatusCode().name()); // CREATED
System.out.println(todoResponse.getStatusCodeValue());   // 201
postForObject metodu - Var arg Parametre
Örnek
Şöyle yaparız
Order order = ...;
PaymentRequest paymentRequest =...;

// Call payment service
Payment payment = restTemplate
  .postForObject("https://payment-service/pay", paymentRequest, Payment.class);
postForObject metodu - Map Parametre
Örnek
Şöyle yaparız.
public void uploadDocument(byte[] fileContents, final String filename) {
  RestTemplate restTemplate = new RestTemplate();
  String fooResourceUrl = "http://localhost:8080/spring-rest/foos"; // Dummy URL.
  MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();

  map.add("name", filename);
  map.add("filename", filename);

  // Here we 
  ByteArrayResource contentsAsResource = new ByteArrayResource(fileContents) {
    @Override
    public String getFilename() {
        return filename; // Filename has to be returned in order to be able to post.
    }
  };

  map.add("file", contentsAsResource);

  // Now you can send your file along.
  String result = restTemplate.postForObject(fooResourceUrl, String.class, map);

  // Proceed as normal with your results.
}

SpringMVC RestTemplate.getForObject metodu

Giriş
İmzası şöyle
// Without parameters
<T> T getForObject(URI url, Class<T> responseType) // Parameter object list as var args <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) // Parameter object list as a map <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
getForObject metodu - Parametresiz
XML veya Json ile nesne elde etmek için kullanılır.
Örnek - Array
Şöyle yaparız.
Computer[] computer = restTemplate.getForObject("http://...", Computer[].class);
Örnek
Şöyle yaparız
Todo[] todos = restTemplate
  .getForObject("https://jsonplaceholder.typicode.com/todos", Todo[].class);

// If we use getForEntity
ResponseEntity<Todo[]> todos = restTemplate
        .getForEntity("https://jsonplaceholder.typicode.com/todos", Todo[].class);
Todo[] todoList = todos.getBody();
System.out.println(todos.getStatusCode().name()); // OK
System.out.println(todos.getStatusCodeValue());   // 200
getForObject metodu - Var arg parametreli
XML veya Json ile nesne elde etmek için kullanılır. Son parametre ?type=Foo şeklindeki url'ye dahil olan parametrelerdir.
Örnek
Şöyle yaparız.
QuoteResponse quoteResponse=    
restTemplate.getForObject("http://quotes.rest/qod.json?category=
{param}", QuoteResponse.class, uriVariables);
Örnek
Şöyle yaparız
String completedStatus = "true";
String userId = "1";

Todo[] todosCompletedOfUser = restTemplate
.getForObject(
 "https://jsonplaceholder.typicode.com/todos?completed={completedStatus}&userId={userId}",
 Todo[].class, completedStatus, userId);
getForObject metodu - Map Parametreli
XML veya Json ile nesne elde etmek için kullanılır. Son parametre yani Map ?type=Foo şeklindeki url'ye dahil olan parametrelerdir.

Örnek - Query Parametes
Şöyle yaparız
Map<String, String> map = new HashMap<>();
map.put("completed", "true");
Todo[] todosCompleted = restTemplate
  .getForObject("https://jsonplaceholder.typicode.com/todos?completed={completed}", 
    Todo[].class, map);
Örnek - Path Parameters
Şöyle yaparız
Map<String, String> map = new HashMap<>(); Todo resource
map.put("id", "1");
Todo todo = restTemplate
    .getForObject("https://jsonplaceholder.typicode.com/todos/{id}", Todo.class, map);
System.out.println(todo);












7 Mart 2023 Salı

SpringWebSocket WebSocketHandler Arayüzü

Giriş
Şu satırı dahil ederiz
import org.springframework.web.reactive.socket.WebSocketHandler;
Örnek
Şöyle yaparız
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
public class ReactiveServerWebSocketHandler implements WebSocketHandler {

    @Override
    public @NotNull
    Mono<Void> handle(@NotNull WebSocketSession session) {
        return session.send(session.receive()
                .map(WebSocketMessage::getPayloadAsText)
                .map(session::textMessage)
        );
    }

    @Override
    public @NotNull
    List<String> getSubProtocols() {
        return WebSocketHandler.super.getSubProtocols();
    }
}

6 Mart 2023 Pazartesi

SpringRetry UniformRandomBackOffPolicy Sınıfı

Örnek
Şöyle yaparız
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
import org.springframework.retry.support.RetryTemplate;

public class PayApiRetryTemplate extends RetryTemplate implements InitializingBean {

  @Override
  public void afterPropertiesSet() throws Exception {
    this.setBackOffPolicy(backOffPolicyWithJitter());
    this.setRetryPolicy(...);
  }

  private BackOffPolicy backOffPolicyWithJitter() {
    UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
    policy.setMaxBackOffPeriod(this.prop.getRetry().getMaxBackoff());
    policy.setMinBackOffPeriod(this.prop.getRetry().getMinBackoff());
    return policy;
  }
}

5 Mart 2023 Pazar

SpringData Flyway application.properties

baseline-on-migrate Alanı
Eğer şu hatayı alıyorsak
Use baseline() or set baselineOnMigrate to true to initialize the schema history table
Şöyle yaparız
spring: 
 flyway: 
 baseline-on-migrate: true
baseline-version Alanı
Şöyle yaparız
spring: 
 flyway: 
 baseline-on-migrate: true 
 baseline-version: 0
Açıklaması şöyle
If not, the scripts named V1__xxxx.sql would not execute. as the baseline-version default is version 1.
callbacks Alanı
Örnek
Şöyle yaparız
spring:
  flyway:
    enabled: true
    schemas: public
    baseline-on-migrate: true
    baseline-version: 0
    clean-disabled: true
    validate-on-migrate: true
    locations: classpath:db.migration
    placeholderReplacement: false
    callbacks: com.xxx.CsvUpsertCallback
SpringData Flyway Callbacks yazısına bakınız

clean-disabled Alanı
Örnek
Şöyle yaparız
spring: 
 flyway: 
 clean-disabled: true
debug
Örnek
Şöyle yaparız
logging: 
 level: 
 root: INFO 
 org: 
 flywaydb: DEBUG
locations Alanı
Normalde classpath:db/migration dizinini taranır.  Çıktısı şöyle
ClassPathScanner : Unable to resolve location classpath:db/callback.
ClassPathScanner : Scanning for classpath resources at 'classpath:db/migration' …
...
Örnek
Şöyle yaparız. Burada flyway'in arayacağı scriptlerin dizini belirtiliyor.
flyway:
  datasources:
    default:
      enabled: true
      baseline-on-migrate: true
      locations: classpath:db/mymigration
group Alanı
Açıklaması şöyle
The property spring.flyway.group set to true indicates we want to run the pending migrations in a single transaction instead of one transaction per pending migrations. So if one migration fails, all the executed migrations before will be rollbacked.
Örnek
Şöyle yaparız
spring.flyway.enabled=true
spring.flyway.url=jdbc:mysql://localhost:3307/blog?serverTimezone=UTC&useSSL=false
spring.flyway.user=root
spring.flyway.password=secretpswd
spring.flyway.group=true
schemas Alanı
Örnek
Şöyle yaparız
# Flyway properties
spring.flyway.enabled=true
spring.flyway.url=YOUR_DB_URL
spring.flyway.password= YOUR_DB_PASSWORD
spring.flyway.user= YOUR_DB_USERNAME
spring.flyway.schemas=migrations
spring.flyway.locations=classpath:db/migration/postgresql
Örnek
Şöyle yaparız
spring: 
 flyway: 
 placeholders: 
 mySchema: yourSchema
Şöyle yaparız
CREATE TABLE ${mySchema}.template 
( 
 id bigint NOT NULL, 
 code character varying(255) NOT NULL, 
 PRIMARY KEY (id) 
);