31 Mart 2023 Cuma

Multi-staged Docker ve SpringBoot

Örnek
Şöyle yaparız
1. Burada fat jar üretiliyor. 
2. Runtime için kullanılan image slim
# First stage: build the application
FROM maven:3.8.3-jdk-11 AS build
COPY . /app
WORKDIR /app
RUN mvn package -DskipTests

# Second stage: create a slim image
FROM openjdk:11-jre-slim
COPY --from=build /app/target/my-application.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
Örnek
Şöyle yaparız
1. Burada build aşamasında mvn dependency:go-offline ile tüm kütüphaneler indirilip docker cache'e alınıyor.
2. fat jar üretiliyor 
3. Runtime için kullanılan image slim değil
FROM openjdk:11 as builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline

COPY src/ ./src/
RUN mvn package -DskipTests

FROM openjdk:11
COPY --from=builder /app/target/my-application.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

SpringCloud LoadBalancer @LoadBalancedAnotasyonu

Örnek - WebClient
Şöyle yaparız
@Configuration
public class HttpLoadBalancedConfiguration {

  @LoadBalanced
  @Bean
  WebClient.Builder webClientBuilder() {
    return WebClient.builder();
  }
}
Örnek - RestTemplate
Elimizde iki tane servis olsun.
// app1
spring.application.name=service1
server.port=8081

// app2
spring.application.name=service2
server.port=8082
Şöyle yaparız
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
  return new RestTemplate();
}

String url = "http://service1/hello";
String result = restTemplate.getForObject(url, String.class);

30 Mart 2023 Perşembe

WireMock WireMockRunner Sınıfı - JUnit4 İçindir

Giriş
Açıklaması şöyle
This will set up a mock server before each test and tear it down after each test.
Örnek
Şöyle yaparız
@RunWith(WireMockRunner.class)
public class MockServerTest {

  @Test
  public void testMockServer() throws Exception {
    stubFor(get(urlEqualTo("/api/endpoint"))
      .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBody("{\"message\": \"Hello, World!\"}")));

    URL url = new URL("http://localhost:8080/api/endpoint");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    connection.connect();

    assertThat(connection.getResponseCode(), is(200));
    assertThat(connection.getHeaderField("Content-Type"), is("application/json"));
    assertThat(new BufferedReader(new InputStreamReader(connection.getInputStream()))
    .lines().collect(Collectors.joining()), is("{\"message\": \"Hello, World!\"}"));
  }
}


WireMock WireMockRule Sınıfı - JUnit4 İçindir

Örnek
Şöyle yaparız
@Rule
public WireMockRule wireMockRule = new WireMockRule(int port);
Açıklaması şöyle
We can integrate a WireMock server into JUnit test cases by using the @Rule annotation. This allows JUnit to manage the life cycle, starting the server prior to each test method and stopping it after the method returns.

Similar to the programmatically managed server, a JUnit managed WireMock server can be created as a Java object with the given port number:

If no arguments are supplied, server port will take the default value, 8080. Server host, defaulting to localhost, and other configurations may be specified using the Options interface.
dropConnectionPercentage - network failures
Örnek
Elimizde şöyle bir kod olsun
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class NetworkFailureTest { @Autowired private TestRestTemplate restTemplate; @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig() .dynamicPort() .networkLatency(1000, TimeUnit.MILLISECONDS) .dropConnectionPercentage(50)); ... }
Testleri şöyle yaparız
@Test
public void testGreeting() { String response = restTemplate.getForObject("/greeting", String.class); assertEquals("Hello, world!", response); } @Test public void testTimeout() { wireMockRule.stubFor(get(urlEqualTo("/greeting")) .willReturn(aResponse() .withStatus(200) .withFixedDelay(2000))); assertThrows(RestClientException.class, () -> { restTemplate.getForObject("/greeting", String.class); }); }
shutdownServer metodu
Şöyle yaparız
@Test
public void testConnectionError() { wireMockRule.shutdownServer(); assertThrows(RestClientException.class, () -> { restTemplate.getForObject("/greeting", String.class); }); }
withFault metodu - CONNECTION ERROR
Örnek
Şöyle yaparız
wireMockRule.stubFor(get(urlEqualTo("/api/data"))
    .willReturn(aResponse()
        .withFault(Fault.CONNECTION_RESET_BY_PEER)));
withStatus metodu - HTTP Errors
Örnek
Şöyle yaparız
wireMockRule.stubFor(get(urlEqualTo("/api/data"))
    .willReturn(aResponse()
        .withStatus(500)));


28 Mart 2023 Salı

WireMock WireMockServer Slow Response

Örnek
Elimizde şöyle bir kod olsun
public class WireMockConfig {
  private WireMockServer wireMockServer;

  public void start() {
    wireMockServer = new WireMockServer(options().port(8080));
    wireMockServer.start();
    configureFor("localhost", 8080);
  }
  public void stop() {
    wireMockServer.stop();
  }
  public void reset() {
    WireMock.reset();
  }
  // Fixed delay
  public void stubSlowResponse() {
    stubFor(get(urlEqualTo("/slow-response"))
                .willReturn(aResponse()
                        .withFixedDelay(5000)
                        .withStatus(200)));
  }
  // Dynamic Delay
  public void stubDynamicDelayResponse() {
    stubFor(get(urlEqualTo("/dynamic-delay-response"))
            .willReturn(aResponse()
                    .withUniformRandomDelay(1000, 5000)
                    .withStatus(200)));
  }
  // Delay distribution
  public void stubLogNormalDelayResponse() {
    stubFor(get(urlEqualTo("/log-normal-delay-response"))
            .willReturn(aResponse()
                    .withLogNormalDistribution(5000, 1.0)
                    .withStatus(200)));
  }
  // Delay sequence
  public void stubDelaySequenceResponse() {
    stubFor(get(urlEqualTo("/delay-sequence-response"))
            .willReturn(aResponse()
                    .withDelaySequence(1000, 2000, 3000, 4000, 5000)
                    .withStatus(200)));
  }
}
Testte şöyle yaparız
@RunWith(SpringRunner.class)
@WebMvcTest(SlowResponseController.class) public class SlowResponseControllerTest { @Autowired private MockMvc mockMvc; @MockBean private RestTemplate restTemplate; @Rule public WireMockRule wireMockRule = new WireMockRule(8080); @Autowired private WireMockConfig wireMockConfig; @Before public void setup() { wireMockConfig.start(); } @Test public void should_return_slow_response() throws Exception { wireMockConfig.stubSlowResponse(); mockMvc.perform(MockMvcRequestBuilders.get("/slow-response")) .andExpect(MockMvcResultMatchers.status().isOk()); verify(getRequestedFor(urlEqualTo("/slow-response"))); } }



WireMock WireMockServer Sınıfı

Giriş
Şu satırı dahil ederiz
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
Mocks From Static Files Kullanımı
HTTP File Server Gibi Kullanım içindir. Yani statik dosyalar içindir. Açıklaması şöyle
The easiest way to create a mock is to create a folder with the name __files and add files into it.
Now we can perform a GET request using the URL:
http://<mock-server-host>:<port>/file-name.file-extension
Örnek 
Şöyle yaparız
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;

import com.github.tomakehurst.wiremock.WireMockServer;

new WireMockServer(options()
  .usingFilesUnderClasspath("src/main/resources/wiremock")
  .port(8000)
).start();
src/main/resources/__files altındaki dosyaları sunmaya başlar. http://localhost:8080/customer.json dosyasını istersek src/main/resources/__files/customers.json dosyasını gönderir.

Mappings Kullanımı
 Açıklaması şöyle
Most of the time we will need mocks that are more advanced than we can get from static files. To create more advanced mocks we can use mappings. Mapping is a file in a JSON format, located in a special folder called mappings, that describes e request and response. This description contains the request URL, request method, request, and response body, headers, and response status. 
Eğer gerekiyorsa POST göndererek, çalışma esnasında mapping yaratmak ta mümkün.

Örnek 
src/main/resources/__files dizininde healthcheck.json isimli dosya şöyle olsun
{
  "request": {
    "method": "GET",
    "url": "/actuator/health"
  },
  "response": {
    "status": 200,
    "body": "{\"status\":\"UP\"}",
    "headers": {
      "Content-Type": "application/json"
    }
  }
}
http://localhost:8080//actuator/health adresine istek gönderirsek, cevap alırız

Response Templating Kullanımı
Açıklaması şöyle
There are cases when we want to return inside the response some data received in the request body, path, or headers. For example, we perform a request to obtain a customer by id:

http://<mock-server-host>:<port>/customers/{id}
And we want to receive a response body, containing the id of the customer passed as a path parameter. To be able to achieve that we can use Handlebars templates.
Transformers Kullanımı
Açıklama burada

Callback Kullanımı
Açıklama burada

Metodlar
constructor - WireMockConfiguration
Şöyle yaparız. Her testing başında bu sınıfın start() ve stop() metodlarını çağırmak gerekir.
var wiremockServer = WireMockServer(WireMockConfiguration.options().dynamicPort())
stubFor metodu
get + willReturn + withBody
get + willReturn + withBodyFile

get + willReturn + aResponse + Çeşitli Delay Yöntemleri

withBody Kullanımı
Örnek
Şöyle yaparız
class HttpTest {

  private WireMockServer server;

  @BeforeEach
  void setUp() {
    server = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
    server.start();
  }

  @Test
  void test() throws Exception {
    mockWebServer();

    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("http://localhost:" + server.port() + "/my/resource"))
                    .build();
    HttpResponse<String> response = client.send(request,
                                                HttpResponse.BodyHandlers.ofString());

    assertEquals("TheGreatAPI.com", response.body());
  }

  private void mockWebServer() {
    server.stubFor(get("/my/resource")
                  .willReturn(ok()
                  .withBody("TheGreatAPI.com")));
  }

  @AfterEach
  void tearDown() {
    server.shutdownServer();
  }
}
withBodyFile Kullanımı
Örnek
Şöyle yaparız
@Slf4j
public class FeignTest {
  private BookClient bookClient;
  private WireMockServer wireMockServer;
  private final static String FOLDER = "src/test/resources/wiremock";

  @BeforeAll
  public void setup() {
    bookClient = Feign.builder().client(new OkHttpClient())
      .encoder(new GsonEncoder()).decoder(new GsonDecoder())
      .logger(new Slf4jLogger(BookClient.class)).logLevel(Logger.Level.FULL)
      .target(BookClient.class, "http://localhost:57002/api/books");

    wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig()
      .withRootDirectory(FOLDER).port(57002)
      .notifier(new ConsoleNotifier(true)));
      wireMockServer.start();
  }

  @Test
  public void findById() throws Exception {
    wireMockServer.stubFor(get(urlPathEqualTo("/api/books/1"))
      .willReturn(aResponse().withBodyFile("book1.json")));

    Book book = bookClient.findById(1);
    assertThat(book.getAuthor(), equalTo("Orson S. Card"));
    wireMockServer.verify(1, getRequestedFor(urlEqualTo("/api/books/1"))
      .withHeader("Accept", WireMock.equalTo("application/json")));
  }
}


SpringKafka Consumer MessageListenerContainer Arayüzü

Giriş
Şu satırı dahil ederiz
import org.springframework.kafka.listener.MessageListenerContainer;
stop metodu
Açıklaması şöyle. Yani amaç Kafka Consumer thread'i durdurmadan ve rebalance işlemini tetiklemeden poll() çağrısına devam etmek ama 0 kayıt çekmek.
But let’s say you trigger a non-deterministic batch job via Kafka. Usually, a Kafka consumer has its processing thread and it doesn’t allow us to use reactive or parallel programming techniques. That means every blocking call you do in consumer, will block the consumer’s thread since that will only block its thread that is not a big deal. But that will cause us to throw away reactive programming’s advantages. For example, what will we do if we want to use Kotlin coroutines in Kafka consumers?

Well, thanks to Spring Kafka we can pause the MessageListenerContainer, process the message, then resume the MessageListenerContainer:

When we paused the container, Spring continues to make poll request in the background. Simply it will poll zero records. Since we did make polling, rebalance won’t be triggered
Örnek
Şöyle yaparız.
@KafkaListener(id="assigned_listener_id", autoStartup = "false",
topics = "topic-to-listen-to")
public void listen(Message message){
  // interesting message processing logic
}

@Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;

//invoke this method to start the listener
public void startListener(){
  kafkaListenerEndpointRegistry.getListenerContainer("assigned_listener_id").start();
}

//invoke this method to stop the listener
public void stopListener(){
  kafkaListenerEndpointRegistry.getListenerContainer("assigned_listener_id").stop(()->{
    log.info("Listener Stopped.");
  });
}

SpringContext SocketUtils Sınıfı

Giriş
Şu satırı dahil ederiz.
import org.springframework.util.SocketUtils;
Testlerde kullanmak için bir de org.springframework.test.util.TestSocketUtils sınıfı var

findAvailableTcpPort metodu
Örnek
Şöyle yaparız
int port = SocketUtils.findAvailableTcpPort();
try (ServerSocket serverSocket = new ServerSocket(port)) {
    assertThat(serverSocket).isNotNull();
    assertThat(serverSocket.getLocalPort()).isEqualTo(port);
} catch (IOException e) {
    fail("Port is not available");
}

27 Mart 2023 Pazartesi

SpringData Jdbc JdbcTemplate Multiple Databases Kullanımı

Örnek
application.properties şöyle olsun
spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1
spring.datasource.primary.username=root
spring.datasource.primary.password=password

spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=password
İki DataSource tanımlarız
@Configuration
public class DataSourceConfig {
    
  @Bean
  @Primary
  @ConfigurationProperties(prefix="spring.datasource.primary")
  public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
  }
    
  @Bean
  @ConfigurationProperties(prefix="spring.datasource.secondary")
  public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
  }
}
İki JdbcTemplateConfig tanımlarız
@Configuration
public class JdbcTemplateConfig {
    
  @Bean
  public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") 
    DataSource primaryDataSource) {
    return new JdbcTemplate(primaryDataSource);
  }
    
  @Bean
  public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") 
    DataSource secondaryDataSource) {
    return new JdbcTemplate(secondaryDataSource);
  }
}
Kullanmak için şöyle yaparız
@Autowired
@Qualifier("primaryJdbcTemplate")
private JdbcTemplate primaryJdbcTemplate;

@Autowired
@Qualifier("secondaryJdbcTemplate")
private JdbcTemplate secondaryJdbcTemplate;

public List<User> getAllUsers() {
    String sql = "SELECT * FROM users";
    return primaryJdbcTemplate.query(sql, new UserMapper());
}

public List<Order> getAllOrders() {
    String sql = "SELECT * FROM orders";
    return secondaryJdbcTemplate.query(sql, new OrderMapper());
}

SpringData Redis ZSetOperations Sınıfı

removeRangeByScore metodu
Örnek
Şöyle yaparız
public Set<String> rateLimiter(String id) {
  String key = "rate-" + id;
  long currentTime = System.currentTimeMillis();
  long slidingWindowTime = 60_000L; //1 min, This is basically the rate limit time.

  List<Object> valOuter =  redisTemplate.execute(new SessionCallback<List<Object>>() {
    @Override
    public List<Object> execute(@NotNull RedisOperations operations) throws DataAccessException {
      operations.multi();
      operations.opsForZSet().removeRangeByScore(key, 0, currentTime - slidingWindowTime);
      operations.opsForZSet().add(key, Long.toString(currentTime), currentTime);
      operations.expire(key, currentTime + slidingWindowTime, TimeUnit.MILLISECONDS);
      redisTemplate.opsForZSet().range(key, 0, -1);
      return operations.exec();
    }
  });
  log.info("EmployeeDao: rateLimiter: {}", valOuter);

  return valOuter != null ? (Set<String>) valOuter.get(valOuter.size()-1) : Collections.emptySet() ;
}


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