22 Mart 2023 Çarşamba

SpringData Flyway @FlywayTest Anotasyonu

Örnek
Şöyle yaparız
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureTestDatabase
@FlywayTest
public class MyIntegrationTests {
  // ...
}
Açıklaması şöyle
@SpringBootTest: This annotation is used to specify that this is a Spring Boot integration test. It loads the complete application context and can be used to test the full functionality of the application.
@ActiveProfiles("test"): This annotation is used to specify that the "test" profile should be activated for this test class. This is useful if you have different profiles for different environments and want to use a specific profile for testing.
@AutoConfigureTestDatabase: This annotation is used to automatically configure a test database for the tests. By default, Spring Boot will configure an in-memory database, but you can also use a real database by specifying the @TestPropertySource annotation with the appropriate properties.
@FlywayTest: This annotation is used to automatically run Flyway migrations before each test method. This will ensure that the database schema is up-to-date and ready for testing. Additionally, it will clean the database by dropping all database objects and running the migrations from scratch.



gRPC Kullanımı

Maven
Şu satırı dahil ederiz
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>2.14.0.RELEASE</version>
</dependency>
Örnek
Şu satırı dahil ederiz
spring-boot-starter-grpc
application.properties
Örnek
Şöyle yaparız
grpc.server.port=9090
grpc.server.inProcessName=test
Açıklaması şöyle
This configuration specifies that the gRPC server will run on port 9090 and has an in-process name of ‘test’.
proto Dosyası
src/main/proto dizinine yerleştirilir
Örnek
Şöyle yaparız
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.grpc";
option java_outer_classname = "GreetProto";

package greet;

// The greeting service definition.
service GreetService {
  // Sends a greeting
  rpc Greet (GreetRequest) returns (GreetResponse);
}

// The request message containing the user's name.
message GreetRequest {
  string name = 1;
}

// The response message containing the greetings.
message GreetResponse {
  string greeting = 1;
}

@GrpcService Anotasyonu
2 tane daha endpoint ekler. Bunlar şöyle
grpc.health.v1.Health                     
grpc.reflection.v1alpha.ServerReflection
grpcurl ile test yapılabilir. Şöyle yaparız
grpcurl --plaintext localhost:9090 list
grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
grpcurl --plaintext -d '{"name": "John"}' localhost:9090 \
  ch.frankel.blog.grpc.model.HelloService/SayHello
Örnek
Şöyle yaparız
import com.example.grpc.*;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class GreetingService extends GreetServiceGrpc.GreetServiceImplBase {
  @Override
  public void greet(GreetRequest request,StreamObserver<GreetResponse> responseObserver) {
    String name = request.getName();
    String greeting = "Hello, " + name + "!";
        
    GreetResponse response = GreetResponse.newBuilder()
      .setGreeting(greeting)
      .build();
        
    responseObserver.onNext(response);
    responseObserver.onCompleted();
  }
}
Unit Test için şöyle yaparız
import com.example.grpc.*;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.testing.GrpcCleanupRule;
import org.junit.Rule;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class GreetingServiceTest {
    
  @Rule
  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
    
  @Test
  public void greet_shouldReturnGreeting() throws Exception {
    // Arrange
    String name = "World";
    GreetingService service = new GreetingService();
    String serverName = InProcessServerBuilder.generateName();
        
    grpcCleanup.register(InProcessServerBuilder
      .forName(serverName)
      .directExecutor()
      .addService(service)
      build()
      .start());
        
    GreetServiceGrpc.GreetServiceBlockingStub stub = GreetServiceGrpc.newBlockingStub(
      grpcCleanup.register(InProcessChannelBuilder.forName(serverName)
        .directExecutor().build()));
        
      // Act
      GreetResponse response = stub.greet(GreetRequest.newBuilder().setName(name)
        .build());
        
      // Assert
      assertEquals("Hello, World!", response.getGreeting());
  }
}
Integration Test için şöyle yaparız
import com.example.grpc.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GreetingServiceIntegrationTest {

  @Autowired
  private GreetServiceGrpc.GreetServiceBlockingStub greetServiceBlockingStub;
    
  @Test
  public void greet_shouldReturnGreeting() {
    // Arrange
    String name = "World";
        
    // Act
    GreetResponse response = greetServiceBlockingStub.greet(GreetRequest.newBuilder()
      .setName(name).build());
        
    // Assert
    assertEquals("Hello, World!", response.getGreeting());
  }
}

15 Mart 2023 Çarşamba

SpringKafka Consumer @KafkaListener Anotasyonu - Manual Commit

Manual Commit
Örnek
application.properties şöyle olsun
spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.consumer.group-id=my-group
Şöyle yaparız
@KafkaListener(topics = "my-topic") public void listen(ConsumerRecord<String, String> record, Acknowledgment ack) { try { // Process the message System.out.println("Received message: " + record.value()); // Manually commit the offset ack.acknowledge(); } catch (Exception e) { // Handle any exceptions } }
Batch Listener
ConcurrentKafkaListenerContainerFactory nesnesinin setBatchListener özelliği etkinleştirilir

Örnek
Şöyle yaparız
@Bean public ConcurrentKafkaListenerContainerFactory<String,String> batchListenerContainerFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); factory.setBatchListener(true); return factory; } @KafkaListener(topics = "my-topic", containerFactory = "batchListenerContainerFactory") public void listen(List<ConsumerRecord<String, String>> records, Acknowledgment ack) { try { for (ConsumerRecord<String, String> record : records) { // Process the message System.out.println("Received message: " + record.value()); } // Manually commit the offset ack.acknowledge(); } catch (Exception e) { // Handle any exceptions } }

SpringBoot Actuator @EndpointWebExtension - Customizing inbuilt endpoints

Giriş
Açıklaması şöyle
It may be the case that you want to change the functionality of an inbuild endpoint such as changing its output. Spring boot actuator allows you to modify its inbuilt endpoints by extending them.

Following are the steps involved:
  1. Create a class with @EndpointWebExtension annotation. You can also annotate it with @EndpointExtension.
  2. @EndpointWebExtension is a specialization of @EndpointExtension for web applications.
  3. For JMX, use @EndpointJmxExtension or @EndpointExtension annotation.
  4. This annotation should have an endpoint property. Its value should be the class name of the endpoint that you want to extend.
  5. For this example, we will be extending info endpoint, so its value will be InfoEndpoint.class.
  6. As with creating a custom endpoint, provide methods annotated with @ReadOperation, @DeleteOperation or @WriteOperation.
Örnek
Şöyle yaparız
@Component
@EndpointWebExtension(endpoint=InfoEndpoint.class)
public class EnvEndpointCustomizer {
  @ReadOperation
  public Environment getEnvironmentInfo() {
    return new Environment();
  }
  // model class for info output
  class Environment {
    String ram;
    String hdd;
    public String getRam() {
      return ram;
    }
    public void setRam(String ram) {
      this.ram = ram;
    }
    public String getHdd() {
      return hdd;
    }
    public void setHdd(String hdd) {
      this.hdd = hdd;
    }
    // constructor
    public Environment() {
      this.ram = "8GB";
      this.hdd = "500GB";
    }
  }
}
http://localhost:8080/actuator/info adresine gidersek çıktısı şöyle olur
{"ram":"8GB","hdd":"500GB"}



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.
}