2 Mart 2021 Salı

SpringWebFlux Flux.fromIterable metodu

Örnek
Şöyle yaparız
List<String> list = Arrays.asList("vins", "guru");
Flux<String> stringFlux = Flux.fromIterable(list)
                        .map(String::toUpperCase);

SpringWebFlux Flux.just metodu - Liste Verilebilir

Giriş
Cold publisher yaratır. Yani her yeni subscriber veriyi tekrar alır.

Örnek
Şöyle yaparız
Flux<Integer> flux = Flux.just(1);
//Observer 1
flux.subscribe(i -> System.out.println("Observer-1 : " + i));
//Observer 2
flux.subscribe(i -> System.out.println("Observer-2 : " + i));

//Output
Observer-1 : 1
Observer-2 : 1
Örnek - delayElements ile Kullanım
Şöyle yaparız. Burada Flux elemanları biraz gecikmeyle veriyor.
System.out.println("Starts");

//flux emits one element per second
Flux<Character> flux = Flux.just('a', 'b', 'c', 'd')
                            .delayElements(Duration.ofSeconds(1));
//Observer 1 - takes 500ms to process
flux
        .map(Character::toUpperCase)
        .subscribe(i -> {
            sleep(500);
            System.out.println("Observer-1 : " + i);
        });
//Observer 2 - process immediately
flux.subscribe(i -> System.out.println("Observer-2 : " + i));

System.out.println("Ends");

Starts
Ends
Observer-2 : a
Observer-1 : A
Observer-2 : b
Observer-1 : B
Observer-2 : c
Observer-2 : d
Observer-1 : C
Observer-1 : D

25 Şubat 2021 Perşembe

SpringWebFlux Mono.flatMap metodu - Asenkron Çalışır

Giriş
flatMap() vs map()
map() metodundan farklı olarak asenkron çalışır. Açıklaması şöyle
There is always a question, is there a difference between flatMap and map? Well yes there is a difference.
- flatMap is used for non blocking operation in this case an operation that will return a Mono or Flux.
- map is used for blocking operation that can be done in fixed time. An example would be transforming an object.
Örnek
Şöyle yaparız. Burada Person nesnesini EnhancedPerson nesnesine çevirme işi senkron yapılır. Ancak veri tabanına kaydetme işinin ne kadar süreceği belirsiz olduğu için asenkron yapılır
return Mono.just(Person("name", "age:12"))
  .map { person ->
    EnhancedPerson(person, "id-set", "savedInDb")
  }.flatMap { person ->
    reactiveMongoDb.save(person)
  };
Örnek
Şöyle yaparız
//Create a person object with names and age Person obj = new Person("firstname", "lastname", 20); return Mono.just(obj) .map { person -> AdminPerson(person, "admin") // blocking transform the person to an admin }.flatMap { person -> personRepository.save(person) // Non blocking save to database }
Örnek - chained flatMap
Şöyle yaparız
@GetMapping("/students/course/{studentID}/{courseID}") Mono<CourseWork> addNewCourseNoChain(Long studentID, @PathVariable Long courseID) { return studentsRepository.findById(studentID) .flatMap(students -> { //Update student details students.setUpdated_on(System.currentTimeMillis()); return studentsRepository.save(students); }).flatMap(updatedStudent -> { //Create a new course for student CourseWork courseWork = getCoursework(updatedStudent.getId(), courseID); return courseWorkRepository.save(courseWork); }); }
Örnek - subscribe farkı
Elimizde şöyle bir kod olsun. service::update() çalışmaz. Çünkü o da bir Mono dönüyor ve bu dönülen Mono nesnesine subscribe olunmadı
interface Service {
Mono<String> create(String s);
Mono<Void> update(String s);
}
class Foo {
private final Service service;
Mono<Void> problem() {
return service
.create("foo")
.doOnNext(service::update)
.then();
}
}
Düzeltmek için şöyle yaparız. Bu sefer çalışır çünkü flatMap() verilen lambda'nın döndürdüğü Mono'ya subscribe olur.
interface Service {
Mono<String> create(String s);
Mono<Void> update(String s);
}
class Foo {
private final Service service;
Mono<Void> problem() {
return service
.create("foo")
.flatMap(service::update)
}
}

24 Şubat 2021 Çarşamba

SpringWebFlux Mono.doOnNext metodu

Giriş
Açıklaması şöyle
Mono::doOnNext triggers when the data is emitted successfully, which means the data is available and present.

Mono::doOnSuccess triggers when the Mono completes successfully - result is either T or null, which means the processing itself successfully finished regardless the state of data and is executed although the data are not available or present but the pipeline itself succeed.

Mono::then as the end of the methods chain returns Mono<Void> on complete and error signals.
Örnek
Şöyle yaparız
Mono.just(5)
  .doOnNext(i -> logger.info(i + ""))     // <-- is called and prints '5'
  .flatMap(i -> Mono.empty())
  .doOnNext(i -> logger.info(i + ""))     // <-- is NOT called
  .doOnSuccess(i -> logger.info("Done"))  // <-- is called and prints 'Done' (i is null)
  .block();
Örnek
Elimizde şöyle bir kod olsun. service::update() çalışmaz. Çünkü o da bir Mono dönüyor ve bu dönülen Mono nesnesine subscribe olunmadı
interface Service {
Mono<String> create(String s);
Mono<Void> update(String s);
}
class Foo {
private final Service service;
Mono<Void> problem() {
return service
.create("foo")
.doOnNext(service::update)
.then();
}
}
Düzeltmek için şöyle yaparız
interface Service {
Mono<String> create(String s);
Mono<Void> update(String s);
}
class Foo {
private final Service service;
Mono<Void> problem() {
return service
.create("foo")
.doOnNext(foo -> service.update(foo).block())
.then();
}
}
Açıklaması öyle
The take away here is to only use doOn* methods for side-effects, e.g. logging, uploading metrics.

SpringCache Hazelcast application.properties Ayarları

Giriş
Burada SpringCache gerçekleştirimi olarak Hazelcast için bazı örnekler var.

Hazelcast ayarları kodla yapılabileceği gibi yaml veya xml ile de yapılabiliyor. yaml veya xml için tek yapmamız gereken bo dosyanın nerede olduğunu Spring'e söylemek

Örnek - yaml
Şöyle yaparız
spring.cache.type=hazelcast
spring.hazelcast.config=classpath:cache.yaml
Örnek - xml
Şöyle yaparız
spring.hazelcast.config=classpath:config/demo-config.xml
Hazelcast ayarları cache.yaml dosyasındadır

cache.yml dosyası
Örnek
Şöyle yaparız
# hazelcast.yaml
hazelcast:
  network:
    join:
      multicast:
        enabled: true
Örnek - Hazelcast Node
Şöyle yaparız
hazelcast:
monitoring: true maxSize: 10000 namespace: hazelcast tcp: enabled: true members: "localhost:5701"
Örnek - Hazelcast Client
Şöyle yaparız
hazelcast:
  initialBackoffMillis: 1000
  maxBackoffMillis: 6000
  multiplier: 2.0
  clusterConnectTimeoutMillis: 50000
  jitter: 0.2
  asyncStartClient: true
  namespace: hazelcast
  userCodeDeploymentEnabled: true
  clientProperties:
    hazelcast.client.invocation.timeout.seconds: 5
  executorServiceName: trader-cli
  tcp:
    enabled: true
    members: localhost:5701

23 Şubat 2021 Salı

SpringWebFlux Request Validation

Giriş
Eğer WebFlux kullanmıyorsak, ResponseEntityExceptionHandler yazısına bakabiliriz.

Örnek
Elimizde şöyle bir kod olsun
public class FormatNameRequest {

  @NotNull(message = "Title cannot be null")
  private String title;

  @NotNull
  @Size(message = "First name must be between 2 and 25 characters", min = 2, max = 25)
  private String firstName;

  private String middleName;

  @NotBlank(message = "Last name cannot be empty")
  private String lastName;
    ...
}
Şöyle yaparız. Burada @Valid anotasyonundan sonra, post edilen parametre Mono<...> şeklinde kodlanıyor
@RestController
public class ValidationDemoController {

  @PostMapping("/format")
  public Mono<ResponseEntity<FormattedNameResponse>> format(@Valid @RequestBody
Mono<FormatNameRequest> request) {
    return request
      .map(res -> ResponseEntity.status(HttpStatus.OK).body(alo
FormattedNameResponseMapper.fromFormatNameRequest(res)))
      .onErrorResume(WebExchangeBindException.class,
        ex -> Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST)
                  .body(FormattedNameResponseMapper.fromWebExchangeBindException(ex))));
    }
}

public class FormattedNameResponseMapper {

  public static FormattedNameResponse fromWebExchangeBindException(
WebExchangeBindException ex) {
    FormattedNameResponse res = new FormattedNameResponse();
    List<Error> errors = ex.getFieldErrors().stream()
      .map(fieldError -> new Error(fieldError.getField(), fieldError.getDefaultMessage()))
      .collect(Collectors.toList());
    res.setErrors(errors);
    return res;
  }
  
  public static FormattedNameResponse fromFormatNameRequest(FormatNameRequest req) {
    String s = String.format("%s %s %s %s", req.getTitle(), req.getFirstName(),
req.getMiddleName(), req.getLastName());
    FormattedNameResponse res = new FormattedNameResponse();
    res.setFormattedName(s);
    return res;
  }
}
Test için şöyle yaparız
Request
POST http://host:port/format
Content-Type: application/json
{
    "title":"Mr",
    "firstName":"Jhon",
    "middleName": "Martin",
    "lastName": "Smith"
}

Succesful Response
200 OK
Content-Type: application/json
{
    "formattedName": "Mr Jhon Martin Smith"
}

Error Response
400 Bad Request
Content-Type: application/json
{
    "errors": [
        {
            "code": "erroCode1",
            "message": "errorMessage1"
        },
        {
            "code": "erroCode2",
            "message": "errorMessage2"
        },
        {
            "code": "erroCode3",
            "message": "errorMessage13"
        }
    ]
}

22 Şubat 2021 Pazartesi

SpringBoot Actuator @Endpoint Anotasyonu - Custom Endpoint İçindir

Giriş
Şu satırı dahil ederiz
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector;
Açıklaması şöyle
Following are the steps to create a new endpoint.
  1. Create a new class. This class should be annotated with @Endpoint annotation.
  2. Provide an id attribute to this annotation. This will be the id of the endpoint or the name with which it will be accessed.
  3. Create a method in this class. This method should have @ReadOperation annotation.
  4. When the endpoint is accessed, the method with @ReadOperation will be invoked. Thus, this method should return the data that you want to send when the endpoint is accessed.
  5. Finally, this class should be a spring bean so it is annotated with @Component annotation.
  6. Register this endpoint id in application.properties to enable it as shown below.management.endpoints.web.exposure.include = info,env,health,getusers
1. @Endpoint olarak işaretli sınıfı bir bean kodlanır. 
2. Bu bean içinde 
- GET isteği için @ReadOperation,
- POST isteği için @WriteOperation
- DELETE isteği için @DeleteOperation 
olarak işaretli metodlar olabilir. Metodlar parametre alabilirler
3. Bu bean application.properties dosyasında Spring'e tanıtılır. 

Örnek
Şöyle yaparız. "http://localhost:8080/actuator" adresindeki listede "helloworld" isimli bir endpoint görebiliriz.
@Configuration
public class CustomActuatorConfiguration {

  @Bean
  public HelloWorldEndpoint helloWorldEndpoint() {
    return new HelloWorldEndpoint();
  }

}

@Endpoint(id = "helloworld")
public class HelloWorldEndpoint {

  @ReadOperation
  public String helloWorld() {
    return "Hello World";
  }
}
application.properties dosyasına şöyle yaparız
management.endpoints.web.exposure.include=helloworld
Sonucu görmek için şöyle yaparız
> curl 'http://localhost:8080/actuator/helloworld'
Hello World
Örnek
Şöyle yaparız
@Component
@Endpoint(id="getusers")
public class LoggedInUserFinder {
  List<User> users = new ArrayList<>();
  @ReadOperation
  public List<User> getUsers() {
    User userOne = new User();
    userOne.setName("Mark Twain");
    User userTwo = new User();
    userTwo.setName("Robinhood");
    users.add(userOne);
    users.add(userTwo);
    return users;
  }
  @ReadOperation
  public User getUser(@Selector String userName) {
    // return user matching name
    for (User user : users) {
      if(user.getName().equals(userName)) {
        return user;
      } 
    }
     return new User();
  }
  @DeleteOperation
  public User removeUser(@Selector String userName) {
    // remove user matching name
    for (User user : users) {
      if(user.getName().equals(userName)) {
        users.remove(user);
        return user;
      } 
    }
     return new User();
  }
  @WriteOperation 
  public User removeUser(@Selector String userName) {
     // create user with name
     User user = new User(); 
     // add user
     users.add(user);
  }
  // User model class
  static class User {
    private String name;
    public String getName() {
      return name;
    }
    public void setName(String name) {
      this.name = name;
    }
  }
}
application.properties dosyasına şöyle yaparız
management.endpoints.web.exposure.include = info,env,health,getusers
http://localhost:8080/actuator/getusers adresine gidersek çıktı şöyle
[
  {"name":"Mark Twain"},
  {"name":"Robinhood"}
]