25 Temmuz 2021 Pazar

SpringBoot AbstractFailureAnalyzer Sınıfı

Giriş
Custom Starter sınıflarda hata oluşursa, daha okunaklı bir çıktı alabilmeyi sağlar.

Örnek
Elimizde şöyle bir kod olsun
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

public class InvalidGreeterSalutationFailureAnalyzer extends AbstractFailureAnalyzer {
  @Override
  protected FailureAnalysis analyze(Throwable rootFailure, InvalidSalutationException cause) {
    return new FailureAnalysis(
      String.format("'%s' is an invalid salutation", cause.getSalutation()),
      "A valid salutation must begin with an upper-case letter",
      cause);
    }
}
Şöyle yaparız
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration

org.springframework.boot.diagnostics.FailureAnalyzer=
com.github.shazin.greeter.starter.config.InvalidGreeterSalutationFailureAnalyzer

19 Temmuz 2021 Pazartesi

SpringScheduling Shedlock Kullanımı

Giriş
Açıklaması şöyle. ShedLock kütüphanesi, distributed lock için veri tabanını kullanıyor.
ShedLock makes sure that your scheduled tasks are executed at most once at the same time. If a task is being executed on one node, it acquires a lock which prevents execution of the same task from another node (or thread). Please note, that if one task is already being executed on one node, execution on other nodes does not wait, it is simply skipped.
Açıklaması şöyle. 
The high level idea of ShedLock is pretty simple. When a given instance is executing a scheduler method of your application, a data source update call will happen with the provided configuration. The configuration will contain a unique name for the scheduler and also it contains 3 other columns lock_until, locked_at, and locked_by. If another instance tries to run the same scheduler method, the first step is to check if it is locked, in case it is locked, then it will not be executed.
Maven
Shedlock 3 kısımdan oluşuyor
1. Core
2. Integration 
3. Lock Provider
Integration ile Core bağımlılığı da geliyor. Lock Provider bağımlılığı ile de arkada kullanılacak veri tabanı seçiliyor.

Örnek - Spring
Şu satırı dahil ederiz. Lock provider olarak kullanılacak kütüphaneyi de dahil etmek gerekiyor. Burada Lock provider olarak JDBC kullanılıyor.
<dependency>
  <groupdId>net.javacrumbs.shedlock</groupdId>
  <artifactId>shedlock-spring</artifactId>
  <version>4.23.0</version>
</dependency>
<dependency>
  <groupdId>net.javacrumbs.shedlock</groupdId>
  <artifactId>shedlock-provider-jdbc-template</artifactId>
  <version>4.23.0</version>
</dependency>
Gradle
Şu satırı dahil ederiz
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.3.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.3.0'
Daha sonra bir tablo oluştururuz
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
  locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
Kullanım
1. Spring'e ait @EnableScheduling anotasyonunun olduğu koda ilave olarak @EnableSchedulerLock anotasyonu tanımlanır. Burada varsayılan ayarlar belirtiliyor
Yani şöyle yaparız
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {
    ...
}
2. Bir LockProvider tanımlanır. Açıklaması şöyle
You’ll need to create a LockProvider bean and specify which lock providers to use (e.g., database-backed locks, ZooKeeper, Redis, etc.).
Lock provider olarak bir sürü farklı şey kullanılabiliyor. Liste burada. 
JdbcTemplateLockProvider yazısına bakabilirsiniz

3. Spring'e ait @Scheduled anotasyonunun olduğu koda anotasyon olarak @SchedulerLock Anotasyonu ilave edilir. Yani şöyle yaparız
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;

...

@Scheduled(...)
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
    // To assert that the lock is held (prevents misconfiguration errors)
    LockAssert.assertLocked();
    // do something
}
@EnableSchedulerLock Anotasyonu
Açıklaması şöyle
We need to enable Shedlock with a default lock for a maximum of 5 minutes.
...
Thus, during the next tick of the scheduler, each instance will try to write a line about itself in the lock table, and the first one who succeeds will continue executing the task. Everyone else will skip the tick.

Lock on the task will hang in our case for a maximum of 5 minutes. Upon completion of the task, the instance will remove the lock in the table. If 5 minutes have passed and the lock has not been removed, other instances will consider that that instance has frozen while the task is being executed.
defaultLockAtLeastFor Alanı
Örnek
Şöyle yaparız
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor="PT20s",defaultLockAtLeastFor="PT5s")
public class SchedulerConfiguration {

  @Bean
  public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(
      JdbcTemplateLockProvider.Configuration.builder()
        .withJdbcTemplate(new JdbcTemplate(dataSource))
        .build()
    );
  }
}
defaultLockAtMostFor Alanı
Örnek
Şöyle yaparız
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "5m")
public class ApplicationConfiguration {
}
Örnek
Şöyle yaparız
@EnableSchedulerLock(defualtLockAtMostFor = "PT45S",defaultLockAtLeastFor = "PT45S")
@Configuration
public class SchedulerLockConfig {
  ...
}

SpringStomp MessageBrokerRegistry Sınıfı - Stomp İçin Kullanılacak Broker Ayarları

Giriş
Şu satırı dahil ederiz
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
enableSimplerBroker metodu
Örnek
Şöyle yaparız. enableSimpleBroker() çağrısı ile Spring'in içindeki basit bir broker etkinleştirilir. Bu broker gerçek bir broker değildir.
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
  config.enableSimpleBroker("/topic");
  config.setApplicationDestinationPrefixes("/app");
}
Örnek
Şöyle yaparız.
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
  registry.enableSimpleBroker("/topic", "/queue");
  registry.setApplicationDestinationPrefixes("/app");
  registry.setUserDestinationPrefix("/user");
}
enableStompBrokerRelay metodu
Açıklaması şöyle
... we can use Spring’s out-of-the-box BrokerRelay implementation that uses an external broker (RabbitMQ, ActiveMQ, etc.) to process WebSockets, manage subscriptions, and more.
Eğer RabbitMQ kullanıyorsak açıklaması şöyle
The point is that RabbitMQ does not allow slashes after standard routes. Thus, if we send a message to the routes /topic/ or /queue/, then there should be no other slashes in the name. The easy way here is to rename all destinations on the frontend and backend sides by replacing the slashes with dots.
Örnek
Şöyle yaparız
@Configuration
public class WebSocketConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableStompBrokerRelay("/topic", "/queue")
      .setClientLogin("guest")
      .setClientPasscode("guest")
      .setSystemLogin("guest")
      .setSystemPasscode("guest")
      .setRelayHost("127.0.0.1")
      .setRelayPort(61613);
  }
}

SpringCache Redis RedisCacheManagerBuilderCustomizer Sınıfı

Örnek
Şöyle yaparız
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
  return builder -> builder
    .withCacheConfiguration(“AccountCache",
      RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(60)))
    .withCacheConfiguration(“ProductCache",
      RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)));
}

16 Temmuz 2021 Cuma

SpringData Liquibase

Giriş
Birden fazla veri tabanını migrate etmek istersek Flyway yetersiz. Liquibase gerekiyor. Açıklaması şöyle
Liquibase facilitates database migrations with database-agnostic formats including XML, YAML, and JSON. When we use non-SQL formats for database migrations, Liquibase generates the database-specific SQL for us. It also supports plain old SQL scripts. Liquibase takes care of variations in data types and SQL syntax for different databases.
Açıklaması şöyle
When you deploy changes, Liquibase creates/updates two tables in your database: DATABASECHANGELOG and DATABASECHANGELOGLOCK.

Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.liquibase</groupId>
  <artifactId>liquibase-core</artifactId>
  <version>${liquibase.version}</version>
</dependency>
application.properties
Bu dosyada master change log belirtilir. Master change log içinde diğer change log'lar yazılır. changelog içinde "change set" dosyaları bulunur

Örnek - master change log
Şöyle yaparız. 
spring:
  liquibase:
    change-log: classpath:/db/changelog/db.changelog-master.xml
    enabled: true
Örnek
Şöyle yaparız
spring:
  datasource:
    hikari:
      connectionTimeout: 20000
      maximumPoolSize: 2
      connectionTestQuery: SELECT 1
    url: jdbc:postgresql://localhost:5432/liquibase_demo_db
    username: postgres
    password: postgres
    driverClassName: org.postgresql.Driver
  liquibase:
    change-log: db/changelog/changelog.yaml
    enabled: true
changelog.yaml şöyledir. Buradaki AT-01-initial.sql ile bir tablo yaratırız. AT-02-alter-table.sql ile bir tabloda değişiklik yaparız
databaseChangeLog:
  - include:
       file: db/changelog/AT-01-initial.sql
  - include:
       file: db/changelog/AT-02-alter-table.sql
Eğer istersek change log dosyaları yerine her şeyi master change log içinde de yapabiliriz. Ancak bence bu tercih edilmemeli. Şöyle yaparız
- changeSet:
    id: 1
    author: leo
    tagDatabase:
      tag: v1
    changes:
      - createTable:
          tableName: one_more_test_table
          columns:
            - column:
                name: id
                type: int
                autoIncrement: true
                constraints:
                  primaryKey: true
                  nullable: false
            - column:
                name: name
                type: varchar(50)
- changeSet:
    id: 2
    author: leo
    tagDatabase:
      tag: v2
    changes:
      - addColumn:
          tableName: one_more_test_table
          columns:
            - column:
                name: age
                type: int
    rollback:
      - dropColumn:
          tableName: one_more_test_table
          columns:
            - column:
                name: age
Change Log Dosyaları
Örnek - db.changelog.xml
Şöyle yaparız. Burada tablo yoksa yaratma gösteriliyor.
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">

  <changeSet author="asadganiev" id="create-user-table">
    <preConditions onFail="MARK_RAN">
      <not>
       <tableExists tableName="users"/>
      </not>
    </preConditions>

    <createTable tableName="users">
      <column name="id" type="bigint" autoIncrement="true">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="username" type="varchar(50)">
        <constraints unique="true" nullable="false"/>
      </column>
      <column name="email" type="varchar(50)">
        <constraints unique="true" nullable="false"/>
      </column>
      <column name="password" type="varchar(64)">
        <constraints nullable="false"/>
      </column>
      <column name="first_name" type="varchar(50)">
        <constraints nullable="false"/>
      </column>
      <column name="last_name" type="varchar(50)">
        <constraints nullable="true"/>
      </column>
      <column name="phone_number" type="varchar(13)">
        <constraints nullable="true"/>
      </column>
    </createTable>
  </changeSet>
</databaseChangeLog>
Örnek
Şöyle yaparız
//base for database migrations
@Slf4j
public class BaseDatabaseMigration {
  protected void migrateFlyway(String folder, DataSource ds) {
    try {
      // Create the Flyway instance and point it to the database
      Flyway flyway = Flyway.configure()
        .baselineOnMigrate(true) //required because of existing objects
        .dataSource(ds)
        .locations("db/"+folder)
        .load();
      // Start the migration
      flyway.migrate();   
            
    } catch (Exception e) {
      log.error("failed to migrate db (Flyway): "+folder, e);
      //need to stop the app
      System.exit(1);
    }
  }
  protected void migrateLiquibase(String folder, String schema, Connection con) {
    try {
      // Create the Liquibase Database and update
      Database database = DatabaseFactory.getInstance()
        .findCorrectDatabaseImplementation(new JdbcConnection(con));
      database.setDefaultSchemaName(schema);
      Liquibase liquibase = new 
        liquibase.Liquibase("db/"+folder+"/db.changelog-master.xml",
new ClassLoaderResourceAccessor(), database);
      liquibase.update(new Contexts(), new LabelExpression());
    } catch (Exception e) {
      log.error("failed to migrate db (Liquibase): "+folder, e);           
      //need to stop the app
      System.exit(1);
    }
  }   
}
lquibase komutu
Örnek - rollback
Şöyle yaparız
liquibase 
  -url=jdbc:postgresql://localhost:5432/liquibase_demo_db 
  -changeLogFile=db/changelog/changelog.yml 
  rollback v1


14 Temmuz 2021 Çarşamba

gRPC GRpcServerBuilderConfigurer Sınıfı

Örnek
Şöyle yaparız
@Component
public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer{
  @Override
  public void configure(ServerBuilder<?> serverBuilder) {
    ((NettyServerBuilder) serverBuilder)
      .permitKeepAliveTime(150, TimeUnit.SECONDS);
  }
  };
}

2 Temmuz 2021 Cuma

SpringBoot Actuator - Heapdump Endpoint

Giriş
Açıklaması şöyle
For a Spring Boot microservice, the easiest way to obtain a heap dump is the heapdump Actuator endpoint, if that is enabled. So you could do a curl or simply open in a browser the URL https://<servicehost>/actuator/heapdump, which will download the heap dump file.
Eğer 503 Service Unavailable alıyorsak bilgisayarda jmap komutu yani JDK yok demektir

Örnek
Şöyle yaparız
curl --location --request GET 'http://localhost:8080/actuator/heapdump' 
     --output myheapdump