9 Ekim 2021 Cumartesi

Swagger @Operation Anotasyonu - Controller'a Eklenir

Giriş
Şu satırı dahil ederiz
import io.swagger.v3.oas.annotations.Operation;
Açıklaması şöyle
In springfox implementation this tag represents the equivalent of the tag @ApiOperation
Açıklaması şöyle
@Operation - Adds details about what the endpoint does, including some properties to achieve this.

summary - The summary (or title) of an endpoint.
description - The description of the endpoint.
tags - What tags to group the endpoint under. You don’t have to specify an array here if there is a single tag; writing tags = "People" also works.
responses - An array of @ApiResponses that details what the endpoint can return, allowing you to show what happens on successful requests as well as unsuccessful or erroneous requests.
operationId Alanı
Örnek
Şöyle yaparız
@Operation(operationId = "deleteJob", summary = "Delete a scheduled job")
responses Alanı
Örnek
Şöyle yaparız
@GetMapping("/{id}")
@Operation( summary = "Finds a person", description = "Finds a person by their Id.", tags = { "People" }, responses = { @ApiResponse( description = "Success", responseCode = "200", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Person.class)) ), @ApiResponse(description = "Not found", responseCode = "404", content = @Content), @ApiResponse(description = "Internal error", responseCode = "500", content = @Content) } ) public ResponseEntity<Person> get(@PathVariable("id") @Parameter(description = "The Id of the person to find.") UUID id) { return personRepository.findById(id).map(ResponseEntity::ok) .orElseThrow(() -> new NoSuchElementException("Person with id: " + id + " does not exist")); }

summary Alanı
Örnek
Şöyle yaparız
@Operation(summary = "Creates a new book")
@ApiResponses(value = {
  @ApiResponse(responseCode = "201", description = "Created book"),
  @ApiResponse(responseCode = "400", description = "Bad request"),
  @ApiResponse(responseCode = "500", description = "Server Error")
@PostMapping(path = "/books", consumes = {"application/json"})
public ResponseEntity<Void> create(@Valid @RequestBody BookDto bookDto) {
  ...
}

4 Ekim 2021 Pazartesi

SpringCloud Jaeger - Distributed Tracing İçindir

Giriş

Open Telemetry + Jaeger
Şeklen şöyle


Gradle
Şöyle yaparız
dependencies {
    implementation "io.micrometer:micrometer-tracing-bridge-otel"
    implementation "io.opentelemetry:opentelemetry-exporter-otlp"
}
Span Exporter için şöyle yaparız
@Bean
public OtlpGrpcSpanExporter otlpHttpSpanExporter(@Value("${tracing.url}") String url) {
  return OtlpGrpcSpanExporter.builder().setEndpoint(url).build();
}
application.properties şöyledir
management.tracing.sampling.probability=1.0
tracing.url=http://localhost:4317

OpenTelemetry vs Open Tracing
Açıklaması şöyle
The community in general is moving towards the OpenTelemetry observability framework. OpenTelemetry will most likely supersede OpenTracing at some point in the future.
OpenTelemetry Nedir?
Açıklaması şöyle
The OpenTelemetry website states that:

"OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software's performance and behavior."

OpenTelemetry was created by merging the popular OpenTracing and OpenCensus projects. It is a standard that integrates with many open source and commercial products written in many programming languages. Implementations of OpenTelemetry are in varying stages of maturity.

At its core, OpenTelemetry contains the Collector, a vendor-agnostic way to receive, process, and export telemetry data.
Trace ve Span
Açıklaması şöyle
A trace tracks the progress of a single request as it passes through services. Distributed tracing is a form of tracing that traverses process, network, and security boundaries. Each unit of work is called a span. A trace is a tree of spans. Think of a distributed trace like a Java stack trace, capturing every component of a system that a request flows through, while also tracking the amount of time spent in and between each component.
Zipkin vs Jaeger
Açıklaması şöyle. Zipkin'ın Twitter, Jaeger'ı ise Uber geliştirmiş.
Zipkin and Jaeger are two popular choices for request tracing. Zipkin was originally inspired by Dapper and developed by Twitter. It's now maintained by a dedicated community. 

Jaeger was originally built and open sourced by Uber. Jaeger is a Cloud Native Computing Foundation project
Açıklaması şöyle
Additionally, while Zipkin is an older project (initiated in 2012 at Twitter) written in Java, Jaeger is a newer project (commenced in 2017 at Uber) developed in Go.

Open Tracing + Jaeger
Burada arada bir OTL Collector yok

Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>io.opentracing.contrib</groupId>
  <artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
  <version>3.3.1</version>
</dependency>
Hem Zipkin hem de  Jaeger kullanan projelerde kendimizin bir tane RestTemplate nesnesi yaratması gerekir. Şöyle yaparız
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
  return builder.build();
}
Açıklaması şöyle. Jaeger'ın araya girip TraceID gönderebilmesini sağlar.
For the spans to get connected to the same trace id, We need to create a RestTemplate bean to allow Jaeger to include an interceptor. This then helps to add traces to the outgoing request which will help to trace the entire request.
Örnek - http
Jaeger sunucusunu belirtmek için şöyle yaparız
opentracing:
  jaeger:
    http-sender:
      url: http://localhost:14268/api/traces
Örnek - udp
Jaeger sunucusunu belirtmek için şöyle yaparız
opentracing:
  jaeger:
    udp-sender:
      host: 127.0.0.1
      port: 6831
    log-spans: true
Şöyle yaparız
@Bean
public JaegerTracer jaegerTracer(){
  return new io.jaegertracing.Configuration("usr-mgt")
    .withSampler(new io.jaegertracing.Configuration.SamplerConfiguration()
                   .withType(ConstSampler.TYPE)
                   .withParam(1))
    .withReporter(new io.jaegertracing.Configuration.ReporterConfiguration()
                   .withLogSpans(true))
    .getTracer();
}

Jaeger Sunucusu
Şeklen şöyle. Dağıtık yapıya sahip

Docker Compose 
Açıklaması şöyle
The Jaeger All-In-One can be easily set up with all the necessary components to test your Java application and ensure that it correctly sends traces. It also includes an OpenTelemetry Collector module for collecting OpenTelemetry traces. 
Örnek
Şöyle yaparız. Daha sonra http://localhost:16686/ adresine gideriz.
version: "3.3"
services:
  jaeger-allinone:
    image: jaegertracing/all-in-one:1.25
    ports:
      - 6831:6831/udp
      - 6832:6832/udp
      - 16686:16686
      - 14268:14268
docker ile çalıştırmak istersek şöyle yaparız
docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 14250:14250 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.24
Örnek - OLTP Collector
Şöyle yaparız
version: '3.7'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686" # the jaeger UI 
      - "4317:4317" # the OpenTelemetry collector grpc 
    environment:
      - COLLECTOR_OTLP_ENABLED=true
Jaeger GUI
http://localhost:16686/ adresine gitmek gerekir. Bir işlemin hangi çağrılarda ne kadar süre tuttuğu gösteriliyor. Şeklen şöyle




30 Eylül 2021 Perşembe

Swagger @ApiResponse Anotasyonu

Giriş
Şu satırı dahil ederiz
import io.swagger.annotations.ApiResponses;
content Alanı
Örnek
Şöyle yaparız
@GetMapping
@Operation(summary = "Get the universities for a given country")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "...",
               content = {@Content(mediaType = "application/json",
                                schema = @Schema(implementation = UniversityDTO.class))}),
  @ApiResponse(responseCode = "400", description = "Invalid id ",content = @Content),
  @ApiResponse(responseCode = "404", description = "...", content = @Content)})
  public List<UniversityDTO> getUniversitiesForCountry(@RequestParam String country) {
    ...
  }
}
responseCode Alanı
Örnek 
Şöyle yaparız
@Operation(summary = "Create customer")
@ApiResponses(value = {
  @ApiResponse(responseCode = "201", description = "Successfully created a customer"),
  @ApiResponse(responseCode = "400", description = "Bad Request"),
  @ApiResponse(responseCode = "401", description = "Authorization denied"),
  @ApiResponse(responseCode = "500", description = "Unexpected system exception"),
  @ApiResponse(responseCode = "502", description = "An error has occurred with an upstream service")
})
@PostMapping(consumes = JSON)
public ResponseEntity createCustomer(@Valid @RequestBody CustomerInfo customerInfo, UriComponentsBuilder uriBuilder)
  throws Exception {
  ...
}

29 Eylül 2021 Çarşamba

SpringShell @ShellMethod Anotasyonu

Giriş
shell:> diye prompt açılır. Eğer buraya help yazarsak "Application Command" ve "Built-In Commands" diye iki tane başlık görürüz. 

"Application Command" altındakiler @ShellMethod ile işaretli bizim metodlarımızdır

"Built-In Commands" altında ise 
clear
exit
help
script
stacktrace komutlarını görürüz

Metodlar içinde org.jline.jline kütüphanesine ait metodlar kullanılabilir. Çünkü SpringShell bu kütüphaneyi de beraberinde getiriyor.

Kullanım
@ShellMethod Anotasyonu, @ShellComponent olarak işaretli bir sınıf içindeki metodlara yazılır

Örnek
Şöyle yaparız.
@ShellComponent
public class SampleCommands {

  @ShellMethod("prints greeting message")
  public String greet() {
    System.out.println("Hi");
  }
}
Örnek - parametre
Şöyle yaparız.
@ShellComponent
public class Cli {
  //Call like : add — a 1 — b 2
@ShellMethod("Add two numbers together") public int add (int a ,int b){ return a + b; } }
Örnek - mandatory 
parametre
Şöyle yaparız. Komut satırından "greet Foo" yazarsak bu metod çalışır
@ShellComponent
public class SampleCommands {

  @ShellMethod("prints greeting message")
  public String greet( @ShellOption(mandatory = true) String name) {
    System.out.println("Hi" + name);
  }
}

SpringData JPA JpaRepository ile @Query ve Stream - Batch İşler İçin Uygundur

Giriş
Veri tabanından veri çekmek için genellikle şu 3 yoldan birisi kullanılıyor
1. Loading everything at once : Bellek yetmeyebilir
2. Using Paging/Slicing : Veriyi dolaşmak için kod yazmak gerekir
3. Using Streams : En kolay yöntem bu

Ne Zaman Stream Kullanılabilir
Açıklaması şöyle. Yani normalde Optional veya List döndürülür ancak Sonuç listesi çok büyükse Stream döndürülebilir
Spring Data JPA repositories in their default setup expects to return instances of either Optional or List, meaning that the result set will be lifted into memory and mapped to your model object. This works well for many use cases, but when the result set becomes very large (> 100,000 records) or the result set size isn’t known in advance, memory will become a problem. Enter Stream.

Kullanım
Stream yöntemin gerçekleşmesi için JPA 2.2'deki Query arayüzünün getResultStream() metodu kullanılıyor. Eğer sadece JPA kullanıyorsak şöyle yaparız
Stream<Author> authors = em.createQuery("SELECT a FROM Author a", Author.class)
  .getResultStream();
Spring ile şöyle yaparız
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

  Stream<Employee> findByHireDateBetween(LocalDate from, LocalDate to);
}

@Autowired
private EmployeeRepository employeeRepo;

@Transactional(readOnly = true)
public void exportEmployees(LocalDate hireDateFrom, LocalDate hireDateTo) {
  try (Stream<Employee> employees = employeeRepo.findByHireDateBetween(hireDateFrom,
                                                                       hireDateTo)) {
    employees.forEach(this::mapAndWrite);
  }
}

private void mapAndWrite(Employee from) {
  ...
}
Eğer N +1 Select problemi varsa şöyle yaparız
@Entity
public class Employee {
  ...

  @OneToMany(mappedBy = "employeeId", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Salary> salaries;

  @OneToMany(mappedBy = "employeeId", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Title> titles;
}

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

  @Query("SELECT DISTINCT e from Employee e "
         + "LEFT JOIN FETCH e.salaries "
         + "LEFT JOIN FETCH e.titles "
         + "WHERE e.hireDate BETWEEN ?1 AND ?2 "
         + "ORDER BY e.employeeId")
  @QueryHints(value = {
    @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE),
    @QueryHint(name = HINT_CACHEABLE, value = "false"),
    @QueryHint(name = HINT_READONLY, value = "true"),
    @QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = "false")
  })    
  Stream<Employee> findByHireDateBetween(LocalDate from, LocalDate to);
}
- Burada join yapıldığı için Employee nesnesi veri tabanından birden fazla gelir. Bunu engellemek için DISTINCT kullanılır
- LEFT JOIN FETCH ile child nesneler eager yüklenir
- ORDER BY niçin lazım tam anlamadım

Genel kullanım için açıklama şöyle
- Any stream operation must be wrapped in a transaction. Spring will throw an exception otherwise
- A stream is created by Spring Data JPA but must be closed by you. A stream keeps a cursor to the result set open since it can’t know when the result set has been consumed. If not closed you will run out of cursors in the database. Use try-with-resources or call close on the stream.
- Forward operations only. A stream can only be consumed once
Yani 
1. Stream mutlaka kapatılmalı
2. Stream'i işleyen kod @Transactional olmalı
3. İşlenen nesne EntityManager'dan detach() çağrısı ile çıkarılmalı
4. N+1 Select problemine dikkat edilmeli


Diğer Parametreler
@QueryHint parametresi de önemli. Kullanılan bazı şeyler şöyle

- HINT_FETCH_SIZE ile bir seferde getirilecek kayıt sayısı belirtilir.

- HINT_PASS_DISTINCT_THROUGH
Açıklaması şöyle. DISTINCT kelimesi veri tabanına gönderilmez, ancak Hibernate bizim için distinct işlemini yapar.
This instruction informs Spring Data JPA/Hibernate not to pass a DISTINCT statement to the database via SQL. Instead it will interpret the DISTINCT statement in our JPQL as an instruction to Hibernate not to return the same entity one time for each row returned, i.e. it is used in conjunction with the instruction regarding DISTINCT explained above.

Örnek
Şöyle yaparız
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.Repository;
import javax.persistence.QueryHint;
import java.util.stream.Stream;

public interface BookRepository extends Repository<Book, Long> {
    
  @QueryHints(value = {
    @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE),
    @QueryHint(name = HINT_CACHEABLE, value = "false"),
    @QueryHint(name = READ_ONLY, value = "true")
  })
  @Query("select b from Book")
  Stream<Book> getAll();
}
Bu repository kullanılırken, EntityManager ara sıra boşaltılmalı. Şöyle yaparız
@Component
public class BookProcessor {
  
  private final BookRepository bookRepository;
  private final EntityManager entityManager;
  
  public BookProcessor(BookRepository bookRepository, EntityManager entityManager) {
    this.bookRepository = bookRepository;
    this.entityManager = entityManager;
  }
  
  @Transactional(readOnly = true)
  public void processBooks() {
    Stream<Book> bookStream = bookRepository.getAll();
  
    bookStream.forEach(book -> {
      // do some processing
      entityManager.detach(book);
    });
    stream.close();
  }
}
Eğer .detach() metodunu çağırmazsak bir yerden sonra OutOfMemoryError hatası alırız. Açıklaması şöyle
The reason for this is that even though we are streaming entities from the database and have marked the query and transaction as read only, Hibernate keeps track of all entities in it’s persistence context. After going through a few thousand records, the heap will be full of these entities.

To resolve this we have to handle each entity in the stream in a different manner and most importantly tell Hibernate that it shouldn’t keep track of the entity in the persistence context once we are done with it.
Örnek
Elimizde şöyle bir kod olsun.
@QueryHints(value = {
  @QueryHint(name = HINT_FETCH_SIZE, value = “600”),
  @QueryHint(name = HINT_CACHEABLE, value = “false”),
  @QueryHint(name = READ_ONLY, value = “true”)
})
@Query(value = “SELECT * FROM journal_entries where accounting_entity_id = :id”, 
       nativeQuery = true)
Stream<JournalEntry> getJournalEntriesForAccountingEntity(Integer id)
Kullanmak için şöyle yaparız.
@Transactional(readOnly = true)
public String generateReportFileUrl(Integer id) throws IOException {
  Stream<JournalEntry> stream = 
    journalEntryManager.getJournalEntryStreamForAccountingEntity(id);
  ...
  stream.close();
}




28 Eylül 2021 Salı

SpringData ChainedTransactionManager Sınıfı - Deprecated

Giriş
Şu satırı dahil ederiz 
import org.springframework.data.transaction.ChainedTransactionManager;
Açıklaması şöyle
The traditional approach, using ChainedKafkaTransactionManager, has been deprecated in 2021. 
Örnek
Açıklaması şöyle
First of all, we need to enable Spring Kafka's Kafka Transaction Manager. We can do it by simply setting the transactional id property in our application.properties:

By setting this property, Spring Boot will automatically configure the Kafka Transaction Manager.
Şöyle yaparız
spring.kafka.producer.transaction-id-prefix=tx-
Sonra şöyle yaparız
@KafkaListener(id = "group1", topics = "topic1")
@Transactional("transactionManager")
public void listen1(String in) {
  log.info("Received from topic1: {}", in);

  log.info("Sending to topic2: {}", in.toUpperCase());
  kafkaTemplate.send("topic2", in.toUpperCase());

  log.info("Writing to database: {}", in);
  demoRepository.save(DemoEntity.builder()
                    .name(in)
                    .timestamp(System.currentTimeMillis())
                    .build()
  );
}
Açıklaması şöyle
The trick here is giving transactionManager as the value for the @Transactional annotation. This is because there will be two transaction managers available: transactionManager and kafkaTransactionManager.

The transactionManager bean is an instance of JpaTransactionManager while the kafkaTransactionManager bean is an instance of KafkaTransactionManager.

And the reason why we want to give the transactionManager as the value for the @Transactional annotation is because our KafkaMessageListenerContainer is already creating transactions for us on consuption. Whenever a new message comes in, it will automatically begin a Kafka transaction before it starts running our method.

Therefore, all we have to do is tell Spring Boot to, before our method is run, to also begin a transaction, but at this time, for the JpaTransactionManager.




SpringBoot DataSource Jasypt - Java Simplified Encryption

Giriş
Bir örnek  burada. application.properties dosyasında şifreli veri kullanıbilmeyi sağlar.

1. Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>com.github.ulisesbocchio</groupId>
  <artifactId>jasypt-spring-boot-starter</artifactId>
  <version>3.0.4</version>
</dependency>
<plugin>
   <groupId>com.github.ulisesbocchio</groupId>
   <artifactId>jasypt-maven-plugin</artifactId>
   <version>3.0.4</version>
</plugin>
2. @EnableEncryptableProperties Anotasyonu
Şöyle yaparız
@SpringBootApplication
@EnableEncryptableProperties
public class MyApplication {
    ...
}
3. application.yml
Şöyle yaparız. Burada şifrelenmesi istenilen şeylere DEC(..) yazılıyor
jasypt:
encryptor: algorithm: PBEWithMD5AndTripleDES iv-generator-classname: org.jasypt.iv.NoIvGenerator password: ${JASYPT_ENCRYPTOR_PASSWORD} spring: datasource: url: jdbc:mysql://localhost:3306/cdr?useSSL=false username: DEC(root) password: DEC(root123)
Dosyayı şifrelemek için şöyle yaparız
mvn jasypt:encrypt -Djasypt.plugin.path=”file:src/main/resources/application.yml”
-Djasypt.encryptor.password=”secretkey”
Eğer application.properties kullanıyorsak dosya ismini vermeye gerek yok. Şöyle yaparız
mvn jasypt:encrypt -Djasypt.encryptor.password=”secretkey”
Uygulamayı JASYPT_ENCRYPTOR_PASSWORD ortam değişkenine, pluginde kullanılan secret key  değerini atayarak çalıştırmak gerekir. Sanırım dosya daha sonra şu hale geliyor. Şifrelenen şeyler ENC(...) haline geliyor
spring:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: none
  datasource:
    name: standalone-client
    url: jdbc:h2:mem:standalone-client
    username: client-user
    # password is client-password
    # provide sample-password as jasypt.encryptor.password to decrypt
    password: ENC(byF912frlZUCifdrOFlGJ8LTjWpVIutKtzU0St2X/hZ9sqRp9kOsg5Se8FVxshFu)
    driverClassName: org.h2.Driver
  h2:
    console:
      enabled: true
  application:
    name: standalone-client

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

logging:
  level:
    root: info