20 Mayıs 2021 Perşembe

SpringWebFlux Flux.onErrorResume metodu

Giriş
Açıklaması şöyle
Project Reactor provides several operators to manage errors effectively within reactive streams:

1. onErrorResume and onErrorReturn: These operators allow you to provide fallback values or alternative streams in case of an error. This can help prevent the entire stream from failing and provide a more graceful degradation.
2. doOnError: This operator lets you execute specific actions when an error occurs, such as logging the error or cleaning up resources. It doesn't interfere with the error propagation itself.
3. retry and retryWhen: These operators enable you to automatically retry an operation a specified number of times or based on a certain condition. This can be helpful for transient errors.
Kısaca onErrorResume exception'ı loglar ve varsayılan bir sonuç döndürür

Örnek
Elimizde şöyle bir kod olsun
Flux<String> stringFlux = Flux.just("A", "B", "C")
  .log()
  .concatWith(Flux.error(new RuntimeException("Exception Occurred")))
  .concatWith(Flux.just("D"))
  .onErrorResume((e) -> {    // this block gets executed
    System.out.println("Exception is : " + e);
    return Flux.just("default", "default1");
});

stringFlux.subscribe(System.out::println);
Çıktı olarak A,B,C,"default","default1" alırız.


13 Mayıs 2021 Perşembe

SpringWebFlux Flux.range metodu - Cold Publisher

Örnek
Şöyle yaparız
Flux<Integer> flux = Flux.range(0, 2)
  .map(i -> {
    return i;
  });

//create a runnable with flux subscription
Runnable r = () -> flux.subscribe(s -> {...});
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");

t1.start();
t2.start();
Örnek 
Şöyle yaparız
Flux<Integer> flux = Flux.range(0, 2)
  .subscribeOn(Schedulers.boundedElastic())
  .map(i -> {
    System.out.println("First Thread : " + Thread.currentThread().getName());
    return i;
  }).
  subscribeOn(Schedulers.single())
    .map(i-> {
     System.out.println("Second Thread : " + Thread.currentThread().getName());
     return i;
  });

flux.subscribe();
Thread.sleep(1000);

SpringWebFlux Flux.takeUntil metodu

Örnek
Şöyle yaparız
Flux.fromIterable(lists)
  .takeUntil(s -> s.equalsIgnoreCase("End"))
  .subscribe(System.out::println);

6 Mayıs 2021 Perşembe

SpringCache @CacheConfig Anotasyonu

Giriş
Eğer nesnenin belli bir isme sahip cache alanına eklenmesini istersek @CacheConfig anotasyonu ile cache ismi belirtilir. Açıklaması şöyle
It is a class level annotation. It is used to share common properties such as cache name, cache manager to all methods annotated with cache annotations.

When a class is declared with this annotation then it provides default setting for any cache operation defined in that class. Using this annotation, we do need to declare things multiple times.
Örnek
Şöyle yaparız. Burada cache ismi her metod için geçerli
@Service
@CacheConfig(cacheNames=”employees”) public class EmployeeService { @Cacheable public Employee findById(int id) { // some code } }
Örnek
Şöyle yaparız
@Service
@CacheConfig(cacheNames = "customerCache")
public class CustomerService {

  @Cacheable(cacheNames = "customers")
  public List<Customer> getAll() {
    ...
  }
}

4 Mayıs 2021 Salı

SpringContext LocaleContextHolder Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.context.i18n.LocaleContextHolder;
getLocale metodu
Şöyle yaparız
Locale locale = LocaleContextHolder.getLocale();

SpringNative

Giriş
Spring 3 ile bu yazıyı iki kısma ayırmak gerekti.

1. Spring 3
Spring 3 SpringNative yazısına taşıdım

2. Spring 3'ten Önce
Daha önceki Spring sürümler için açıklama şöyle
Spring Boot offers two alternatives to create native binaries:
1. A system-dependent binary: this approach requires a local GraalVM installation with the native-image extension. It will create a non-cross-platform system-dependent binary.
For this, Spring Boot has a dedicated profile:
./mvnw -Pnative package

2. A Docker image: this approach builds a containerized version of the application. It requires a local image build, e.g., Docker. Internally, it leverages CNCF Buildpacks (but doesn’t require pack).
Spring Boot provides a Maven target for this:
./mvnw spring-boot:native-image
Eğer yerel GraalVM kurulumunu seçeceksek ilave olarak native-image extension da kurulmalıdır

Her iki yöntem için de 
1. dependency olarak spring-native eklenir. 
2. plugin  olarak şu eklenir
maven için : spring-aot-maven-plugin 
gradle için :  org.springframework.experimental.aot
Açıklaması şöyle
The Spring AOT plugin will automatically be run in the build pipeline to create a special Spring Boot JAR which is needed to run when compiled to a native image. It will try to create all needed reachability configurations for your program to work correctly as a native image.
reflection-config.json Dosyası
SpringNative reflection-config.json Dosyası yazısına taşıdım

Maven
Örnek
Şöyle yaparız
<dependency>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-native</artifactId>
  <version>${spring-native.version}</version>
</dependency>
<plugin>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-aot-maven-plugin</artifactId>
  <version>${spring-native.version}</version>
  <executions>
    <execution>
      <id>test-generate</id>
      <goals>
        <goal>test-generate</goal>
      </goals>
    </execution>
    <execution>
      <id>generate</id>
        <goals>
          <goal>generate</goal>
        </goals>
      </execution>
  </executions>
</plugin>
Gradle
Örnek
Şöyle yaparız
plugins {
  id 'org.springframework.boot' version '2.6.4'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
  id 'org.springframework.experimental.aot' version '0.11.3'
}

group = 'com.springnative.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
  maven { url 'https://repo.spring.io/release' }
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
  useJUnitPlatform()
}

tasks.named('bootBuildImage') {
  builder = 'paketobuildpacks/builder:tiny'
  environment = ['BP_NATIVE_IMAGE': 'true']
}
Şöyle yaparız
./gradlew nativeRun
build-image seçeneği
Projeyi maven ile derlesek bir tane docker image üretir. Bunu derleyip çalıştırmak için şöyle yaparız
$ mvn clean package
$ mvn spring-boot:build-image

$ docker run --name spring-native-example -p 8080:8080 spring-native-example:0.0.1-SNAPSHOT
Örnek
Şöyle yaparız
//Build a normal Spring Boot image
mvn spring-boot:build-image -DskipTests

//Build a Spring Boot native image
mvn -Pnative spring-boot:build-image -DskipTests
native-image seçeneği
Şöyle yaparız
./mvnw spring-boot:native-image
@TypeHint Anotasyonu
Kodda reflection configuration belirtmeyi sağlar
Örnek - AccessBits.FULL_REFLECTION 
Şöyle yaparız
@SpringBootApplication
@NativeHint(options = ["--enable-https"])                        <1>
@TypeHint(
    types = [
        Model::class, Data::class, Result::class, Thumbnail::class,
        Collection::class, Resource::class, Url::class, URI::class
    ],
    access = AccessBits.FULL_REFLECTION                          <2>
)
class BootNativeApplication {...}
Örnek
Şöyle yaparız
@TypeHint(
  types = [News::class],
  access = [TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.PUBLIC_METHODS]
)
@SpringBootApplication
class SpringBootKotlinReactiveApplication





SpringData Multitenancy

Giriş
Multitenancy için 3 yöntem var. Bunlar şöyle
Separate database 
Separate schema
Shared schema
Multitenancyi için spring ve hibernate'i ayrı ayrı ayarlamak lazım

Not : Webflux MongoDB için örnek burada

Spring
Elimizde şöyle bir kod olsun
public abstract class TenantContext {
 
  public static final String DEFAULT_TENANT_ID = "public";
  private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
 
  public static void setCurrentTenant(String tenant) {
    currentTenant.set(tenant);
  }
 
  public static String getCurrentTenant() {
    return currentTenant.get();
  }
 
  public static void clear() {
    currentTenant.remove();
  }
}
Bu kodu dolduracak bir interceptor yazılır. Açıklaması şöyle
In the earlier versions of Spring Boot, we could extend the org.springframework.web.servlet.handler.HandlerInterceptorAdapter class, but in newer versions, the class is deprecated,...
Yani org.springframework.web.servlet.AsyncHandlerInterceptor kullanılır. Şöyle yaparız
@Component
public class TenantRequestInterceptor implements AsyncHandlerInterceptor {
        
  private SecurityDomain securityDomain;
        
  public TenantRequestInterceptor(SecurityDomain securityDomain) {
    this.securityDomain = securityDomain;
  }
 
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
    return Optional.ofNullable(request)
      .map(req -> securityDomain.getTenantIdFromJwt(req))
      .map(tenant -> setTenantContext(tenant))
      .orElse(false);
  }
 
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
    TenantContext.clear();
  }
         
  private boolean setTenantContext(String tenant) {
    TenantContext.setCurrentTenant(tenant);
    return true;
  }
}
Interceptor eklenir
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
 
  @Autowired
  private TenantRequestInterceptor tenantInterceptor;
        
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(tenantInterceptor).addPathPatterns("/**");
  }      
}

1. Separate database
Hibernate tarafından sağlanan AbstractMultiTenantConnectionProvider sınıfından kalıtan yeni bir ConnectionProvider yazılır. Şeklen şöyle



Bu sınıfı kodlarken şu metodlar override edilir
protected ConnectionProvider getAnyConnectionProvider();
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier);
Örnek
Şöyle yaparız
public class SchemaMultiTenantConnectionProvider extends 
  AbstractMultiTenantConnectionProvider {
        
  public static final String HIBERNATE_PROPERTIES_PATH = "/hibernate-%s.properties";
  private final Map<String, ConnectionProvider> connectionProviderMap;
 
  public SchemaMultiTenantConnectionProvider() {
    this.connectionProviderMap = new HashMap<String, ConnectionProvider>();
  }
        
  @Override
  protected ConnectionProvider getAnyConnectionProvider() {
    return getConnectionProvider(TenantContext.DEFAULT_TENANT_ID);
  }
 
   @Override
   protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
     return getConnectionProvider(tenantIdentifier);
  }
        
  private ConnectionProvider getConnectionProvider(String tenantIdentifier) {
    return Optional.ofNullable(tenantIdentifier)
                   .map(connectionProviderMap::get)
                   .orElseGet(() -> createNewConnectionProvider(tenantIdentifier));
  }
 
   private ConnectionProvider createNewConnectionProvider(String tenantIdentifier) {
     return Optional.ofNullable(tenantIdentifier)
                    .map(this::createConnectionProvider)
                    .map(connectionProvider -> {
                      connectionProviderMap.put(tenantIdentifier, connectionProvider);
                      return connectionProvider;
                     })
                     .orElseThrow(() -> 
                        new ConnectionProviderException(
                          String.format("Cannot create new connection provider 
                                         for tenant: %s", tenantIdentifier))
                    );
  }
        
  private ConnectionProvider createConnectionProvider(String tenantIdentifier) {
    return Optional.ofNullable(tenantIdentifier)
                   .map(this::getHibernatePropertiesForTenantId)
                   .map(this::initConnectionProvider)
                   .orElse(null);
  }
        
  private Properties getHibernatePropertiesForTenantId(String tenantId) {
    try {
      Properties properties = new Properties();
      properties.load(getClass().getResourceAsStream(
        String.format(HIBERNATE_PROPERTIES_PATH, tenantId)));
      return properties;
    } catch (IOException e) {
      throw new RuntimeException(
     String.format("Cannot open hibernate properties: %s", HIBERNATE_PROPERTIES_PATH));
    }
  }
 
   private ConnectionProvider initConnectionProvider(Properties hibernateProperties) {
     DriverManagerConnectionProviderImpl connectionProvider = 
      new DriverManagerConnectionProviderImpl();
     connectionProvider.configure(hibernateProperties);
     return connectionProvider;
   }
}
Açıklaması şöyle
The only thing we have to implement in this class are the methods getAnyConnectionProvider() and selectConnectionProvider(String tenantId).

The first method sets up a connection to the database when the tenantId is not set. This happens when the application is starting. Validating whether the tenant is set can be implemented in AsyncHandlerInterceptor on the Spring side and throws an exception when it’s not set or is incorrect.

The second method is responsible for returning the appropriate ConnectionProvider for the indicated tenantId. The solution assumes that we collect ConnectionProvider on a map so as not to create a new one every time. If it’s not on the map yet, then we create a new one and add it to the map. Of course, this can be moved to the classic cache, where you can additionally manage lifetime (TTL).