20 Ekim 2021 Çarşamba

TransactionSynchronizationManager.registerSynchronization metodu - Post Commit İçin Kullanılır

Giriş
Şu satırı dahil ederiz
import org.springframework.transaction.support.TransactionSynchronizationManager;
Transaction bitince çalıştırılacak kodu belirtir. Bu iş zor olduğu için kendimiz bir anotasyon oluşturup yapabiliriz. Bir örnek burada.

Örnek
Şöyle yaparız.
TransactionSynchronizationManager.registerSynchronization(
  new TransactionSynchronization(){
    void afterCommit(){
      //do what you want to do after commit
    }
});
Örnek
Şöyle yaparız
TransactionSynchronizationManager.registerSynchronization(
  new TransactionSynchronization(){
    void afterCommit(){
      // do what you want to do after commit
      // in this case call the notifyUI method
    }
});

18 Ekim 2021 Pazartesi

SpringContext CommonAnnotationBeanPostProcessor Sınıfı

Giriş
Şu satırı dahil ederizz
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
Bu sınıf sayesinde @EJB anotasyonunu Spring bean içinde bile kullanabiliriz. Bu sınıf @EJB anotasyonunu görünce, belirtilen nesnesi JNDI'dan bulup getirir.

17 Ekim 2021 Pazar

spring-boot-gradle-plugin Fat Jar

bootRun Goal
Örnek
Spring projesini çalıştırmak için şöyle yaparız
./gradlew bootRun
bootBuildImage Goal
Açıklaması şöyle. Yani Dokcer image içinde BellSoft Liberica JDK kullanır
You may build a Docker image by defining a Dockerfile where the various layers of the Docker image need to be defined. The downside of this approach is that creating a Dockerfile needs a good understanding of the Docker technology. To empower the containerization, Cloud Native Buildpacks by Pivotal transform your source code into an OCI (Open Container Initiative) compatible Docker image without the need of a Dockerfile. The container image can then be deployed in any modern cloud.

Paketo.io is a Cloud Foundry project and one of the most popular implementations of Cloud Native Buildpacks. It can transform the source code of major programming languages into a container image.

Spring Boot natively supports the buildpacks that create an image with BellSoft Liberica JDK. It also looks at the build.gradle file and the Spring configuration file to build the Docker image.
Örnek
Şöyle yaparız
> sudo ./gradlew bootBuildImage
Çıktısı şöyle
> Task :bootBuildImage 
Building image 'docker.io/library/microservice-customer:1.0.0'   
> Pulling builder image 'docker.io/paketobuildpacks/builder:base' ..................................................  
> Pulled builder image 'paketobuildpacks/builder@sha256:35e29183d1aec1b4d79ebec4fb47ef309dc4e803e2706b5a7336d8ebe68053e8'  
> Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' ..................................................  
> Pulled run image 'paketobuildpacks/run@sha256:d968d1e9827704283bdfd678d9cb2b85d6e0bd826b0cb1f14bbceb5bb6e0f571'  
> Executing lifecycle version v0.10.2  
> Using build cache volume 'pack-cache-9d89fc6213b6.build'   
> Running creator
.
.
.
Açıklaması şöyle
If you look at the output, it is evident that in the first step, it runs the Paketo BellSoft Liberica Buildpack 7.0.0, which in turn downloads the BellSoft Liberica JDK and JRE implementations for the JVM (version 11, as defined in the build.gradle file) from GitHub.

Thus it provides BellSoft Liberica JRE 11.0.10 as the JVM runtime layer of the Docker image.

Next the command runs Paketo Spring Boot Buildpack 4.0.0. It creates the layers for the application, dependencies, and the spring-boot-loader module.

Finally, it creates a Docker image. For our demo, the Customer microservice container image looks as such: docker.io/library/microservice-customer:1.0.0

Then we create one for the Order microservice going through the similar step: docker.io/library/microservice-order:1.0.0

Örnek
Şöyle yaparız
./gradlew bootBuildImage --imageName=splitdemo/spring-boot-docker

12 Ekim 2021 Salı

SpringSecurity OAuth2 Resource Server Kullanımı

Giriş
1. HttpSecurity sınıfının oauth2ResourceServer() metodu ile ayarlar yapılır. Bu metodu bir OAuth2ResourceServerConfigurer döndürür.

2. application.properties dosyasında ayarları yapılır

3. WebSecurityConfigurerAdapter sınıfına @EnableWebSecurity ve  eğer metod level security istiyorsak @EnableGlobalMethodSecurity(jsr250Enabled = true) anotasyonları eklenir.

SpringBoot Actuator - Metrics Endpoint - Micrometer Metriclerini Gösterir

Giriş
Açıklaması şöyle. Micrometer kütüphanesi aracılığıyla metric üretir.
Spring Boot Actuator provides dependency management and auto-configuration for Micrometer, an application metrics facade that supports numerous monitoring systems.
Açıklaması şöyle. Yani aslında Micrometer bir nevi SLF4J gibi düşünülebilir.
Both Quarkus and Spring use the Micrometer metrics library for collecting metrics. Micrometer provides a vendor-neutral interface for registering dimensional metrics and metric types, such as counters, gauges, timers, and distribution summaries. These core types are an abstraction layer that can be adapted and delegated to specific implementations, such as Prometheus.

The Quarkus Micrometer extension automatically times all HTTP server requests. Other Quarkus extensions also automatically add their own metrics collections. Applications can add their own custom metrics as well. Quarkus will automatically export the metrics on the /q/metrics endpoint. In Spring, Micrometer is supported by the Spring Boot Actuator starter if the Micrometer libraries are included on the application's classpath.
Çıktı Çeşitler
Şöyledir. Yani çok fazla çeşit ve formatta çıktı verebiliyor
Atlas
AWS CloudWatch
Datadog
Dynatrace
Elastic
Graphite
Influx
Instana
JMX
New Relic
OpenTelemetry Protocol
Prometheus
StatsD
DataDog Formatı
Metrics endpoint istenirse DataDog formatında çıktı da verebilir. DataDog yazısına taşıdım

Prometheus Formatı
Metrics endpoint istenirse Prometheus formatında çıktısı da verebilir.  Prometheus Endpoint yazısına taşıdım

Etkinleştirmek
Etkinleştirmek için şöyle yaparız
management.endpoint.metrics.enabled = true
management.endpoints.web.exposure.include = metrics
tags alanı
Prometheus için tag belirtir. Açıklaması şöyle
management.metrics.tags.application - we may monitor multiple applications on our Grafana dashboard, so we need to distinguish one application from another

Örnek
Şöyle yaparız
spring:
  application:
    name: [SERVICE_NAME]
...

management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    tags:
      application: ${spring.application.name}
Örnek
Şöyle yaparız.
http://localhost:8080/actuator/metrics
Çıktı olarak şunu alırız. Burada toplanan tüm metriklerin isimleri var
"names": [
    "jvm.threads.states",
    "process.files.max",
    "jvm.memory.used",
    "jvm.gc.memory.promoted",
    "jvm.memory.max",
    "system.load.average.1m",
    ...
  ]
}
Eğer kullanılan diğer kütüphaneler de Micrometer metric üretiyorsa onları da görürüz. 

JVM Çıktısı
JVM İçin Metrics Endpoint yazısına taşıdım

Örnek - Hikari
Hikari ve Tomcat metric üretir. Şöyle yaparız
curl -s http://localhost:8080/actuator/metrics | jq
{
  "names": [
    "hikaricp.connections",
    "hikaricp.connections.acquire",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "hikaricp.connections.idle",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "hikaricp.connections.pending",
    "hikaricp.connections.timeout",
    "hikaricp.connections.usage",
    "http.server.requests",
    "jdbc.connections.active",
    "jdbc.connections.idle",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "spring.data.repository.invocations",
    "tomcat.sessions.active.current",
    "tomcat.sessions.active.max",
    "tomcat.sessions.alive.max",
    "tomcat.sessions.created",
    "tomcat.sessions.expired",
    "tomcat.sessions.rejected"
  ]
}

Örnek - Tek Metric Değerini Görmek
Şöyle yaparız
curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.active | jq
{
  "name": "hikaricp.connections.active",
  "description": "Active connections",
  "baseUnit": null,
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 4
    }
  ],
  "availableTags": [
    {
      "tag": "pool",
      "values": [
        "HikariPool-1"
      ]
    }
  ]
}
Örnek - Tek Metric Değerini Görmek
Şöyle yaparız
http://localhost:8080/actuator/metrics/cache.size
Örnek - Tek Metric Değerini Görmek
Detayları görmek için şöyle yaparız.
http://localhost:8080/actuator/metrics/system.cpu.count
Örnek  - Tek Metric Değerini Görmek
Detayları görmek için şöyle yaparız.
/actuator/metrics/http.server.requests

11 Ekim 2021 Pazartesi

SpringCloud LoadBalancer ServiceInstanceListSupplier Arayüzü

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
get metodu - Request
Açıklaması şöyle
IgniteServiceInstanceListSuppler will return instances based on the request key.
Şöyle yaparız
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;

public class IgniteServiceInstanceListSuppler implements ServiceInstanceListSupplier {
  private final IgniteEx ignite = ...;
  private volatile List<ServiceInstance> instances = ...;
  @Override
  public String getServiceId() {
    return ignite.localNode().consistentId().toString();
  }
  @Override
  public Flux<List<ServiceInstance>> get() {
    return Flux.just(instances);
  }
  @Override
  public Flux<List<ServiceInstance>> get(Request req) {
    if (req.getContext() instanceof RequestDataContext) {
      HttpHeaders headers = ((RequestDataContext)req.getContext()).
        .getClientRequest().getHeaders();
      String cacheName = headers.getFirst("affinity-cache-name");
      String affinityKey = headers.getFirst("affinity-key");

      Affinity<Object> affinity = affinities.computeIfAbsent(
        cacheName, k -> ((GatewayProtectedCacheProxy)ignite.cache(cacheName)).
          context().cache().affinity()
      );

      ClusterNode node = affinity.mapKeyToNode(affinityKey);
      if (node != null)
        return Flux.just(singletonList(toServiceInstance(node)));
    }
    return get();
  }
}


SpringCloud LoadBalancer - Client Side Load Balancing İçindir

Giriş
Açıklaması şöyle
Spring Cloud provides several ways to implement load balancing, including Ribbon, Spring Cloud Load Balancer, Spring Cloud Gateway, and Kubernetes Load Balancer.
Bu proje, WebClient ile client side load balancing yapmak içindir.

Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
LoadBalancerClientFactory Sınıfı
Örnek
Şöyle yaparız
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
  Environment environment,
  LoadBalancerClientFactory loadBalancerClientFactory) {

  String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  return new RoundRobinLoadBalancer(
    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), 
    name);
}
@LoadBalancedAnotasyonu
@LoadBalancedAnotasyonu yazısına taşıdım

@LoadBalancerClient Anotasyonu
configuration Alanı
Load Balancing için iki gerçekleştirim var

1. RoundRobinLoadBalancer 
2. RandomLoadBalancer 

Örnek - RoundRobinLoadBalancer 
Şöyle yaparız
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@LoadBalancerClient(name = WebClientConfig.CLIENT_NAME, 
                    configuration = IgniteLoadBalancerConfiguration.class)
public class WebClientConfig {
    public static final String CLIENT_NAME = "client";

    @Bean
    @LoadBalanced
    public WebClient.Builder usersClientBuilder() {
        return WebClient.builder()
            .defaultHeader("affinity-cache-name", "UserCache");
    }
}
Yardımcı kod şöyledir
@Configuration
public class IgniteLoadBalancerConfiguration {
  public static final String SERVICE_ID = "example";

  @Bean
  @Primary
  public ServiceInstanceListSupplier serviceInstanceListSupplier(IgniteEx ignite) {
    return new IgniteServiceInstanceListSuppler(ignite);
  }

  @Bean
  public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
    Environment environment,
    LoadBalancerClientFactory loadBalancerClientFactory) {
    String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    return new RoundRobinLoadBalancer(
      loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
        name);
  }
}
Açıklaması şöyle
IgniteServiceInstanceListSuppler will return instances based on the request key.
Örnek
Elimizde şöyle bir kod olsun. locahhost üzerinde çalışan 4 tane port döndürür
@Bean
public ReactiveDiscoveryClient customDiscoveryClient() {
  return new ReactiveDiscoveryClient() {
    @Override
    public String description() {
      return "Calling another API example";
    }

    @Override
    public Flux<ServiceInstance> getInstances(String serviceId) {
      log.debug("getInstances: {}", serviceId);

      return Flux.just(8080, 8081, 8082)
        .map(port -> new DefaultServiceInstance(serviceId + "-" + port, serviceId,
          "localhost", port, false));
      }

    @Override
    public Flux<String> getServices() {
      return Flux.just("ExampleApi");
    }
  };
}
Açıklaması şöyle
Implement theReactiveDiscoveryClient . It allows us to return a list of instances that can be used when a specific call is triggered.
Şöyle yaparız
@Service
public class AccountsService {
  private static final String API = "ExampleApi";
  private final WebClient apiClient;

  public AccountsService(WebClient.Builder loadBalancedWebClientBuilder) {
    this.apiClient = loadBalancedWebClientBuilder
      .build();
  }

  public Mono<String> login(String username) {
    return apiClient.post()
      .uri(uriBuilder -> uriBuilder
                        .host(API)
                        .path("/login")
                        .build())
    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .header("username", username)
    .exchangeToMono(r -> handleResponse(r));
  }
  private Mono<String> handleResponse(ClientResponse r) {
    if (r.statusCode().is2xxSuccessful()) {
      return r.bodyToMono(String.class);
    }

    return r.bodyToMono(String.class)
      .switchIfEmpty(Mono.error(new IllegalStateException("Failed: " + r.statusCode())))
      .flatMap(response -> Mono.error(new IllegalStateException("Failed: " +
        r.statusCode() + ", " + response)));
  }
}