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"}
]




















19 Şubat 2021 Cuma

SpringData JpaRepository Derived Query - OrderBy

Giriş
Açıklaması şöyle.
Sorting Derived Query Results
Spring Data JPA also allows us to enable static ordering by appending an OrderBy clause to the query method that references a property and by providing a sorting direction (Asc or Desc).

The following example uses static ordering to retrieve all User entities whose name contains a given value in the ascending order:
List<User> findByNameContainingOrderByName(String name);
// OR
List<User> findByNameContainingOrderByNameAsc(String name);

By default, the OrderBy clause sorts the results in the ascending order. But you can add Desc to reverse the sorting direction:
List<User> findByNameContainingOrderByNameDesc(String name);

If you need dynamic ordering, you can add a Sort parameter to your query method. This is one of the special parameters supported by Spring Data JPA. Sort triggers the generation of an ORDER BY clause. Here is an example:

List<User> findByNameContaining(String name, Sort sort);
To call the above method, you need to create a Sort object to specify the entity attributes and their ordering:
// sort users in ascending order
List<User> users = userRepository.findByNameContaining("john",Sort.by("name").descending());
// sort users in decending order
List<User> users = userRepository.findByNameContaining("john",Sort.by("name").descending());
// multiple sort parameters
List<User> users = userRepository.findByNameContaining("john", Sort.by("name","age").descending());
Örnek
Şöyle yaparız
@Entity
public class WishList {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  @Column(name = "created_date")
  private Date createdDate;
  ...
}

@Repository
public interface WishListRepository extends JpaRepository<WishList, Integer> {
    
  //Method for fetching the wishlist of a particular user and order it by created_date
  List<WishList> findAllByUserIdOrderByCreatedDateDesc(Integer userId);
    
}

18 Şubat 2021 Perşembe

SpringStomp SockJS İstemcisi

Örnek
Mesajı dinleyen STOMP dinleyicisi şöyledir. Burada SpringBoot uygulamasındaki servlet şöyle. 
server.servlet.context-path=/consumer
Burada Spring Servlet "/consumer" adresini dinliyor. STOMP broker'da "/consume" adresini dinliyor. Dolayısıyla istemci "/consumer/consume" adresine bağlanıyor.
var stompClient = null;
function connect(){
    var socket = new SockJS('/consumer/consume');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, onConnected, onError);
}
function onError(event){
    alert(event);
}
function onConnected() {
    stompClient.subscribe('/topic.socket.rabbit', onMessageReceived);
}
function onMessageReceived(payload) {
    var message = JSON.parse(payload.body);
    var tableBody = document.getElementById("tableBody");
    var newRow = tableBody.insertRow();
    var cell1 = newRow.insertCell(0);
    cell1.appendChild(document.createTextNode(message.message));
    var cell2 = newRow.insertCell(1);
    cell2.appendChild(document.createTextNode(message.time));
}
window.onload = connect;
Örnek
Şöyle yaparız
var stompClient = null;

function connect(event) {
  var socket = new SockJS('/websocket');
  stompClient = Stomp.over(socket);

  stompClient.connect({}, onConnected, onError);
  event.preventDefault();
}

function onConnected() {
  // Subscribe to the Public Topic
  stompClient.subscribe('/topic/public', onMessageReceived);

  // Tell your username to the server
  stompClient.send("/app/chat.register",
    {},
    JSON.stringify({sender: username, type: 'JOIN'})
  )
}

function onError(error) {...}

function send(event) {
  var messageContent = messageInput.value.trim();

  if(messageContent && stompClient) {
    var chatMessage = {
      sender: username,
      content: messageInput.value,
      type: 'CHAT'
    };
   stompClient.send("/app/chat.send", {}, JSON.stringify(chatMessage));
  }
  event.preventDefault();
}

function onMessageReceived(payload) {
  var message = JSON.parse(payload.body);
  if(message.type === 'JOIN') {
    ...
  } else if (message.type === 'LEAVE') {
    ...
  } else {
    ...
}
usernameForm.addEventListener('submit', connect, true)

SpringStomp WebSocketMessageBrokerConfigurer Arayüzü

Giriş 
Şu satırı dahil ederiz. In-Memory Broker başlatır. Yani bu sınıf STOMP istemcisinin bağlanacağı adresi ve kuyruk isimlerini belirtir.
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
registerStompEndpoints metodu
Örnek
Şöyle yaparız. SockJS istemcisi "/websocket" adresine bağlanacaktır. Publish edilen mesajları dinlemek için "/topic" adresini dinlemek gerekir
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/websocket").withSockJS();
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic");
    registry.setApplicationDestinationPrefixes("/app");
  }
}
Örnek - In-Memory Broker
Şöyle yaparız. SockJS istemcisi "/consume" adresine bağlanacaktır.  Publish edilen mesajları dinlemek için "/topic.socket.kafka", "/topic.socket.rabbit", "/topic.socket.active" adreslerini dinlemek gerekir
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/consume").withSockJS();
  }
  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic.socket.kafka", "/topic.socket.rabbit",
"/topic.socket.active");
  }
}
Burada STOMP abonelerinin mesaj gönderme yeteneği yok. Sadece Kafka ve RabbitMQ'dan gelen mesajları dinliyorlar. Mesaj gönderme için şöyle yaparız

SpringWebFlux Flux.fromStream metodu

Giriş
fromArray(), fromIterable() gibidir.

Örnek
Eğer birden fazla observer varsa "stream has already been operated upon or closed" hatası alırız
Bu hatayı görmek için şöyle yaparız
List<String> list = Arrays.asList("vins", "guru");
Flux<String> stringFlux = Flux.fromStream(list.stream())
                                .map(String::toUpperCase);

//observer-1
stringFlux
        .map(String::length)
        .subscribe(i -> System.out.println("Observer-1 :: " + i));
//observer-2
stringFlux
        .subscribe(i -> System.out.println("Observer-2 :: " + i));
Hatayı düzeltmek için Supplier<Stream> kullanılır. Şöyle yaparız
Flux.fromStream(() -> list.stream())
     .map(String::toUpperCase);
Örnek
Şöyle yaparız
fun getFileDetail(name: String): Mono<FileDetail> {
  return Flux.fromStream(provideStream())
    .filter { path ->
            path.fileName.toString() == name
    }.map { path ->
            val size = FileChannel.open(path).size()
            val fileName = path.fileName.toString()
            FileDetail(fileName, size)
     }
    .toMono() 
}

17 Şubat 2021 Çarşamba

WireMock Kullanımı - Sunucudan Gelen Cevap Gibidir

Giriş
WireMock yerine bir diğer seçenek MockServer

Açıklaması şöyle
... sets up a mock server locally and allows to easily define requests and responses.
Açıklaması şöyle
WireMock is a great API mocking library. It is written in Java and used mostly by Java developers. But because it relies on easy to use configuration, it can be used by anyone who has to mock API.

WireMock allows you not also to stub responses by endpoint URL, you can also put it between the client and your existing API. With proxying some of the requests can be still handled by your API server, while others are mocked by WireMock.
Şu satırı dahil ederiz
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
Eğer testlerde  bir tane gerçek bean yerine test bean kullanmak istersek şöyle yaparız
spring:
  main:
    allow-bean-definition-overriding: true
Maven
Örnek
Şöyle yaparız
<dependency>
  <groupId>com.github.tomakehurst</groupId>
  <artifactId>wiremock</artifactId>
  <version>2.27.2</version>
  <scope>test</scope>
</dependency>
Örnek - Fat Jar
Bu kütüthanenin groupId değeri com.github.tomakehurst yerine artık org.wiremock oldu. Şöyle yaparız
<dependency>
  <groupId>org.wiremock</groupId>
  <artifactId>wiremock-standalone</artifactId>
  <version>2.27.2</version>
  <scope>test</scope>
</dependency>
Örnek - Java 8 Çok Eski Kullanmayın
Şöyle yaparız
<dependency>
  <groupId>com.github.tomakehurst</groupId>
  <artifactId>wiremock-jre8</artifactId>
  <version>2.28.1</version>
  <scope>test</scope>
</dependency>
Gradle
Şöyle yaparız
dependencies {
  testImplementation 'com.github.tomakehurst:wiremock:2.27.2'
}
Kullanım
WireMockServer nesnesi yaratmak gerekir. 
1. Bu nesneyi sarmalayan bir başka sınıf yaratılarak tüm testlere alt yapı oluşturulabilir
2. Junit 5 Extension kullanılabilir

Örnek - Record Playback İle Kullanım
Açıklaması şöyle
WireMock has a feature called Record and Playback. It can create stub mappings automatically from requests it has received. Combined with its proxying feature this allows us to “record” stub mappings from interaction with existing APIs. When in action, we have the option to either call the external API and capture the response or use one of the earlier recorded stubs to reply to the caller without calling the external API.
Şu satırı dahil ederiz
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.recording.RecordSpec;
import com.github.tomakehurst.wiremock.recording.RecordingStatus;
Örnek
Elimizde şöyle bir konfigürasyon olsun. Bunu okuyan kendi kodumuz var.
wiremock-config:
  proxies:
    - name: university
      port: 9081
      url: http://universities.hipolabs.com
      recording: true

universitiesBaseURL: http://localhost:9081
Şöyle yaparız. Burada önce gerçek cevap kaydediliyor.
@ActiveProfiles(value = "integration")
@SpringBootTest(classes = SpringBootWiremockApplication.class, 
  webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Slf4j
@AutoConfigureMockMvc
class SpringBootWiremockApplicationTests {
  @Autowired
  private MockMvc mockMvc;
  
  @Autowired
  private WireMockConfig wireMockConfig;

  
  private final List<WireMockServer> servers = new ArrayList<>();


  Function<WireMockProxy, WireMockServer> getMockServer =
    (WireMockProxy proxy) ->
      new WireMockServer(
        WireMockConfiguration.options()
          .port(proxy.getPort())
          .notifier(new ConsoleNotifier(true)));

  @BeforeEach
  void startRecording() {
    List<WireMockProxy> proxies = wireMockConfig.getProxies();
    if (!CollectionUtils.isEmpty(proxies)) {
      for (WireMockProxy proxy : proxies) {
        WireMockServer wireMockServer = getMockServer.apply(proxy);
        wireMockServer.start();
        if (proxy.isRecording()) {
          wireMockServer.startRecording(config(proxy.getUrl(), true));
        }
        servers.add(wireMockServer);
      }
    }
  }

  @AfterEach
  void stopRecording() {
     if (!CollectionUtils.isEmpty(servers)) {
       for (WireMockServer server : servers) {
         if (server.getRecordingStatus().getStatus().equals(RecordingStatus.Recording)) {
           server.stopRecording();
         }
         server.stop();
       }
    }
  }

  private RecordSpec config(String recordingURL, boolean recordingEnabled) {
    return WireMock.recordSpec()
	  .forTarget(recordingURL)
	  .onlyRequestsMatching(RequestPatternBuilder.allRequests())
	  .captureHeader("Accept")
	  .makeStubsPersistent(recordingEnabled)
	  .ignoreRepeatRequests()
	  .matchRequestBodyWithEqualToJson(true, true)
	  .build();
  }

}

Örnek - Unit Test İle Kullanım
Test başında @BeforeEach veya @BeforeAll ile sunucuyu başlatırız. Şöyle yaparız
@BeforeEach
void setUp() {
  server = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
  server.start();
}
Test sonunda @AfterEach veya @AfterAll ile sunucuyu kapatırız. Şöyle yaparız
@AfterEach
void tearDown() {
  server.shutdownServer();
}
Örnek - JUnit 5 Extension
Şöyle bir JUnit 5 extension olsun
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;

public class WireMockExtension
        implements AfterAllCallback, AfterEachCallback, ParameterResolver {

  private WireMockServer wiremockServer;

  @Override
  public void afterAll(ExtensionContext context) {
    wiremockServer.shutdown();
  }

  @Override
  public void afterEach(ExtensionContext context) {
    wiremockServer.resetAll();
  }

  @Override
  public WireMockServer resolveParameter(ParameterContext parameterContext, 
    ExtensionContext arg1) 
  throws ParameterResolutionException {

    wiremockServer = new WireMockServer(options()
      .notifier(new ConsoleNotifier(true))
      .dynamicPort());
    wiremockServer.start();
    return wiremockServer;
  }

  @Override
  public boolean supportsParameter(ParameterContext parameterContext, 
    ExtensionContext context) 
  throws ParameterResolutionException {
    return
      parameterContext.getParameter().getType().equals(WireMockServer.class)
      && parameterContext.isAnnotated(WireMockInstance.class);
    }
}
Şöyle bir anotasyonumuz olsun
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface WireMockInstance {

}
Test sınıfının iskeleti şöyle olsun
@ExtendWith({WireMockExtension.class, SpringExtension.class})
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {
                "logging.level.root=INFO",
                "zephyr.api.username=zephyrTestUser",
                "zephyr.api.password=zephyrTestPassword",
                "slack.app.oauth.accessToken=testFakeToken"
        })
@ContextConfiguration(initializers = AppIntegrationTestPropertyInitializer.class)
public class IntegrationTest {

}
//Because WireMock servers start on random ports, we need to set up our Jira 
//and Slack API base URLs on the fly. 
//This is done using initializers in @ContextConfiguration
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.support.TestPropertySourceUtils;

class AppIntegrationTestPropertyInitializer implements 
  ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
      "zephyr.api.baseUrl="
        + String.format("%s/jira/rest/zapi/latest",
                        IntegrationTest.zephyrWiremockServer.baseUrl()));

    TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
      "slack.baseUrl="
        + String.format("%s/api",
                         IntegrationTest.slackWiremockServer.baseUrl()));
    }
}
BeforeAll ve AfterEach şöyle olsun
@BeforeAll
public static void beforeAll(@WireMockInstance WireMockServer server1, @WireMockInstance WireMockServer server2) {
  zephyrWiremockServer = server1;
  slackWiremockServer = server2;

  zephyrWiremockServer.addMockServiceRequestListener(new RequestListener() {
    Override
    public void requestReceived(Request request, Response response) {
      // if we would fail directly here in listener, JUnit error would be really hard to understand (as no response would be generated)
      // therefore saving the value to assert it later in main test flow
      isZephyrAuthSingleValued = request.getHeaders().getHeader("Authorization").isSingleValued();
    }
  });

  slackWiremockServer.addMockServiceRequestListener(new RequestListener() {
    @Override
    public void requestReceived(Request request, Response response) {
      // if we would fail directly here in listener, JUnit error would be really hard to understand (as no response would be generated)
      // therefore saving the value to assert it later in main test flow
      isSlackAuthSingleValued = request.getHeaders().getHeader("Authorization").isSingleValued();
     }
   });
  }

@AfterEach
public void afterEach() {
  assertTrue(isZephyrAuthSingleValued,
    "There must be only one 'Authorization' header in all Zephyr requests");
  assertTrue(isSlackAuthSingleValued,
    "There must be only one 'Authorization' header in all Slack requests");

  zephyrWiremockServer.verify(anyRequestedFor(anyUrl())
    .withBasicAuth(new BasicCredentials("zephyrTestUser", "zephyrTestPassword")));

  slackWiremockServer.verify(anyRequestedFor(anyUrl())
    .withHeader("Authorization", equalTo("Bearer testFakeToken")));
}
@Test
public void testAuthenticatedCallToJiraAndSlack() throws IOException {
  // mock Zephyr API response:
  String zephyrResponse = getResourceFileContent("IntegrationTest/testAuthenticatedCallToJiraAndSlack/zephyr_list_executions.json");
  zephyrWiremockServer.stubFor(get(urlEqualTo("/jira/rest/zapi/latest/execution?issueId=5112096"))
    willReturn(aResponse()
    .withStatus(200)
    .withHeader("Content-Type", "application/json;charset=utf-8")
    .withBody(zephyrResponse)));

  // mock Slack API response:
  String slackResponse = getResourceFileContent("IntegrationTest/testAuthenticatedCallToJiraAndSlack/slack_response.json");
  slackWiremockServer.stubFor(post(urlEqualTo("/api/chat.postMessage"))
    .withRequestBody(matchingJsonPath("$[?(@.channel == 'testing-channel')]"))
    .willReturn(aResponse()
    .withStatus(200)
    .withHeader("Content-Type", "application/json;charset=utf-8")
    .withBody(slackResponse)));


  // test
  String result = controller.getResource();

  // verify
  assertEquals("OK result", result);
}

private String getResourceFileContent(String resourceName) throws IOException {
  URL url = Resources.getResource(resourceName);
  return Resources.toString(url, StandardCharsets.UTF_8);
}

WireMock API

WireMockServer Sınıfı
WireMockServer Sınıfı yazısına taşıdım

16 Şubat 2021 Salı

SpringData Cassandra

Maven
Örnek
Şu satırı dahil ederiz
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
Örnek - reactive
Şu satırı dahil ederiz
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
 <version>2.0.0.M7</version>
</dependency>
application.yml
Örnek
Şöyle yaparız
cassandra:
 personalization:
 cluster-name: personalization
 contact-points: 172.X.X.X, 172.X.X.Y
 keyspace-name: personalization
Birden Fazla Cassandra Keyspace Kullanmak
Burada bir örnek var

ReactiveCassandraRepository Arayüzü
Örnek 
Şöyle yaparız
@Repository
public interface UserPreferencesRepository extends
 ReactiveCassandraRepository<UserPreferences, UserId> {

  @Query(value = “SELECT * FROM user_preferences where user_id = ?0 limit ?1”)
  Flux<UserPreferences> findByUserId(int userId, int limit);

}