31 Ekim 2022 Pazartesi

Flux.flatMap metodu

Giriş 
Bu metodun 3 tane farklı imzası var. Bunlar şöyle
public final <R> Flux<R> flatMap(Function<? super T,? extends Publisher<? extends R>> mapper)

public final <V> Flux<V> flatMap(Function<? super T,? extends Publisher<? extends V>> mapper,
                                 int concurrency)

public final <V> Flux<V> flatMap(Function<? super T,? extends Publisher<? extends V>> mapper,
                                 int concurrency,
                                 int prefetch)
Örnek
Şöyle yaparız
Flux<Integer> numbers = Flux.just(1, 2, 3);
Flux<String> letterFlux = numbers.flatMap(number -> 
  Flux.just("A", "B").map(letter -> number + letter));


27 Ekim 2022 Perşembe

MockServer - Sunucudan Gelen Cevap Gibidir

Giriş
MockServer yerine bir diğer seçenek WireMock
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>org.mock-server</groupId>
  <artifactId>mockserver-netty</artifactId>
  <version>5.13.0</version>
</dependency>
<dependency>
  <groupId>org.mock-server</groupId>
  <artifactId>mockserver-client-java</artifactId>
  <version>5.13.0</version>
</dependency>
Örnek
Şu satırı dahil ederiz
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.mock-server</groupId>
  <artifactId>mockserver-spring-test-listener-no-dependencies</artifactId>
  <version>5.14.0</version>
</dependency>
MockServerClient Sınıfı
Şu satırı dahil ederiz
import org.mockserver.client.MockServerClient;
when metodu
Şöyle yaparız
import static org.hamcrest.Matchers.is;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.nimbusds.jose.jwk.RSAKey;
import com.pohorelov.medium.configuration.SecurityConfiguration;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.json.JSONObject;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.client.MockServerClient;
import org.mockserver.springtest.MockServerTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

@ActiveProfiles("test")
@MockServerTest
@WebMvcTest(controllers = BaseController.class)
class BaseControllerTest {

  private MockServerClient mockServerClient;

  @Autowired
  private MockMvc mockMvc;

  private static String jwks;
  private static String jwt;

  @BeforeAll
  public static void setUp() {
    final var keyPair = generateKeyPair();
    jwks = generateJwksJson(keyPair);
    jwt = authorize(keyPair);
  }

  @BeforeEach
  public void setUpServer() {
    mockServerClient
        .when(
            request()
                .withMethod("GET")
                .withPath("/auth/realms/test-realm/protocol/openid-connect/certs"))
        .respond(response().withStatusCode(200).withBody(jwks));
  }
 

  @SneakyThrows
  private static KeyPair generateKeyPair() {
    final var keyGenerator = KeyPairGenerator.getInstance("RSA");
    keyGenerator.initialize(1024);
    return keyGenerator.generateKeyPair();
  }

  private static String generateJwksJson(KeyPair keyPair) {
    final var rsaKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()).build();
    final var jwk =
        Map.of(
            "kid", "test",
            "kty", "RSA",
            "alg", "RS256",
            "n", rsaKey.getModulus().toString(),
            "e", rsaKey.getPublicExponent().toString());
    final var responseSet = Map.of("keys", List.of(jwk));
    final var jsonObj = new JSONObject(responseSet);
    return jsonObj.toString();
  }

  @SneakyThrows
  private static String authorize(KeyPair keyPair) {
    return Jwts.builder()
        .setHeaderParam("kid", "test")
        .signWith(SignatureAlgorithm.RS256, keyPair.getPrivate())
        .claim("accountId", "1")
        .claim("permissions", "base-controller-read")
        .setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
        .compact();
  }

}
Test kodu için şöyle yaparız
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;


@SneakyThrows
@Test
void shouldReturnAccountData() {
  final var headers = new HttpHeaders();
  headers.setBearerAuth(jwt);
  mockMvc.perform(get("/accounts/1").headers(headers))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.accountName", is("Oleksandr Pohorelov")));
}

@SneakyThrows
@Test
void shouldReturnForbidden() {
  final var headers = new HttpHeaders();
  headers.setBearerAuth(jwt);
  mockMvc.perform(get("/accounts/2").headers(headers))
    .andExpect(status().isForbidden())
    .andExpect(jsonPath("$.errorMessage",
      is("Search account id is not associated with current user")));
}
verify metodu
Örnek
Şöyle yaparız. Böylece MockServer "http://localhost:1080/api/helloWorld" adresini dinler
public class MockServerConfig {
  private static final Integer MOCK_SERVER_PORT = 1080;
  private final ClientAndServer clientAndServer;
  private final MockServerClient mockServerClient = new MockServerClient("localhost", 
    MOCK_SERVER_PORT);

  MockServerConfig() {
    this.clientAndServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
  }

  public void registerHelloWorldEndpoint() {
    mockServerClient
      .when(
        request()
          .withMethod("GET")
          .withPath("/api/helloWorld"),
        exactly(1)
      )
      .respond(
        response()
          .withStatusCode(200)
          .withBody("Mocked Response!!!!!!")
          .withDelay(TimeUnit.SECONDS, 1)
      );
  }

  public void verifyHelloWorldEndpoint() {
    mockServerClient.verify(
      request()
        .withMethod("GET")
        .withPath("/api/helloWorld"),
      VerificationTimes.exactly(1)
    );
  }

  public void stopServer() {
    clientAndServer.stop();
  }
}
Test içinde kullanmak için şöyle yaparız
@SpringBootTest
@AutoConfigureMockMvc
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DemoControllerE2ETest {
  
  private MockServerConfig mockServerConfig;
  @BeforeAll
  public void startServer() {
    mockServerConfig = new MockServerConfig();
    mockServerConfig.registerHelloWorldEndpoint();
  }
  @AfterEach
  public void tearDown() {
    mockServerConfig.verifyHelloWorldEndpoint();
  }
  @AfterAll
  public void tearDown() {
    mockServerConfig.stopServer();
  }
  ...
}


24 Ekim 2022 Pazartesi

SpringCloud Consule

Giriş
Şeklen şöyle

Maven
Şöyle yaparız
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
    
...
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
...
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
application.yaml şöyledir
# enable all the actuator endpoints
management:
  endpoints:
    web:
      exposure:
        include: "*"
        
# spring cloud configuration, exposes 
# the micrometer metrics and registers to consul
spring:
  application:
    name: my-service
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        # instance-id must not be empty, must start with letter, 
        # end with a letter or digit, and have as interior characters 
        # only letter, digits, and hyphen
        instance-id: ${spring.application.name}-${node.name}-micrometer-instance
        serviceName: ${spring.application.name}
        register: true
      retry: 
        initial-interval: 2000
        max-attempts: 10000
        max-interval: 4000
        multiplier: 1.1




20 Ekim 2022 Perşembe

Postman form-data POST - Form Gönderme

Giriş
Bir form gönderme içindir. Form içinde json alan gönderileceği gibi dosya da yüklenebilir.

Örnek - Dosya İçeren Form
Dosya yükleme için şöyle yaparız
import org.springframework.web.multipart.MultipartFile;
class User {
  public String userId;
  public String emailId;
  public MultipartFile profilePicImageFile;
}

@PostMapping("/upload/post")
public ResponseEntity<String> uploadProfilePic(@ModelAttribute User user) 
  throws Exception {
  
}
Örnek - Sadece Form
Şeklen şöyledir


@RestController
@RequestMapping("/")
@AllArgsConstructor
public class NotificationController {

  @GetMapping
  public String sendNotification(@RequestParam String message,
                                 @RequestParam NotificationType notificationType) {
    ...
  }
}

Örnek - Sadece Dosya
Dosya yükleme için şöyle yaparız

public List<InputFile> addFile(@RequestParam("files")MultipartFile[] files){
  return fileService.uploadFiles(files);
}





17 Ekim 2022 Pazartesi

Feature Flags veya Feature toggles - Runtime Feature Toggling

Giriş
Martin Fowler'ın  açıklaması şöyle
A powerful technique, allowing teams to modify system behavior without changing code.

1. Unleash Kütüphanesi
Gitlab ile hazır geliyor. Tek yapmamız gerek Gitlab üzerinde bir Feature Flag yaratmak. Eğer Gitlab kullanımıyotsak Unleash sunucusunu bizim çalıştırmamız gerekiyor.

Maven
Şu satırı dahil ederiz
<dependency>
<groupId>io.getunleash</groupId> <artifactId>unleash-client-java</artifactId> <version>6.1.0</version> </dependency>
Bir bean yaratırız
@Bean
public Unleash unleash () {
  UnleashConfig unleashConfig = UnleashConfig.builder()
    .appName(APP_DEPLOYMENT_ENVIRONMENT)
    .instanceId(INSTANCE_ID)
    .unleashAPI(GITLAB_UNLEASH_URL)
    .build();
  return new DefaultUnleash(unleashConfig);
}
Açıklaması şöyle
APP_DEPLOYMENT_ENVIRONMENT : The environment in which the application will be ran. Note this should also be one of the environments scopes configured on the Feature Flag. This value can be automatically populated from the spring-active-profile production, staging, qa etc or you can make it an environment variable in the various environments.
INSTANCE_ID: The gitlab unleash instance id.
GITLAB_UNLEASH_URL: The API URL as shown in the below image.
Kullanmak için şöyle yaparız
@Autowired
private Unleash unleash

...

if(unleash.isEnabled("new-feature-a")) {
  log.info("Executing new-feature-a");
} else {
  log.info("Still running old feature");
}
2. togglz Kütüphanesi
İlk defa burada gördüm

Maven
Şu satırı dahil ederiz
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-boot-starter</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-console</artifactId>
<version>3.2.1</version>
</dependency>



14 Ekim 2022 Cuma

SpringCache JCache Kullanımı

Giriş
Spring org.springframework.cache.jcache.JCacheCacheManager ile iletişime geçer. Bu sınıfta kendisine takılan ve JCache standardını gerçekleştiren herhangi bir kütüphaneyi kullanır. Açıklaması şöyle
JCache is bootstrapped through the presence of a javax.cache.spi.CachingProvider on the classpath (that is, a JSR-107 compliant caching library exists on the classpath),...
Açıklaması şöyle
JCache is the standard caching API for Java. It is provided by javax.cache.spi.CachingProvider.

It is present on the classpath. The spring-boot-starter-cache provides the JCacheCacheManager.
Maven
Şöyle yaparız. İlave olarak JCache Provider'ın dependency'si de eklenir
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>
Örnek - Hazelcast
Şöyle yaparız
debug=true
spring.cache.type=jcache
spring.cache.jcache.config=classpath:hazelcast.xml
spring.cache.jcache.provider=com.hazelcast.cache.impl.HazelcastServerCachingProvider
Örnek - Hazelcast
Şöyle yaparız
# Configure the cache
#spring.cache.jcache.provider=com.hazelcast.client.cache.HazelcastClientCachingProvider
spring.cache.jcache.provider=com.hazelcast.cache.HazelcastMemberCachingProvider

Örnek - XML ile Hazelcast
Şöyle yaparız. Burada JCache Standardını gerçekleştiren org.springframework.cache.jcache.JCacheCacheManager nesnesine bir com.hazelcast.cache.HazelcastCacheManager nesnesi geçiliyor
<cache:annotation-driven cache-manager="cacheManager" />

<hz:hazelcast id="instance">
    ...
</hz:hazelcast>

<hz:cache-manager id="hazelcastJCacheCacheManager" instance-ref="instance" 
  name="hazelcastJCacheCacheManager"/>

<bean id="cacheManager" class="org.springframework.cache.jcache.JCacheCacheManager">
    <constructor-arg ref="hazelcastJCacheCacheManager" />
</bean>

SpringCache Couchbase

Giriş
Açıklaması şöyle
Couchbase is a NoSQL database that can act as cache provider on top of the spring boot cache abstraction layer.

The CouchbaseCacheManager is automatically configured when we implement couchbase-spring-cache and configure couchbase.
Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>com.couchbase.client</groupId>
  <artifactId>couchbase-spring-cache</artifactId>
</dependency>



12 Ekim 2022 Çarşamba

src/main/resources/META-INF/spring.schemas ve spring.handlers Dosyaları

Giriş
Bu iki dosya da Spring konfigürasyonun XML ile yapıldığı zamanlardan kalma. Yani kendi XML yapımız var ve onu bileşenlerine ayırarak (parse etmek) bir bean oluşturmak istiyoruz


spring.schemas Dosyası
Eğer kendi namespace'imiz varsa bunu Spring ile kullanabilmek için yaparız

Örnek
spring.schemas şöyledir. Böylece belirtilen isime denk gelen xsd dosyası belirtilir
https\://www.hazelcast.com/schema/spring/hazelcast-spring-5.3.xsd=hazelcast-spring-5.3.xsd
http\://www.hazelcast.com/schema/spring/hazelcast-spring.xsd=hazelcast-spring-5.3.xsd
https\://www.hazelcast.com/schema/spring/hazelcast-spring.xsd=hazelcast-spring-5.3.xsd
spring.handlers ise şöyledir.
http\://www.hazelcast.com/schema/spring=com.hazelcast.spring.HazelcastNamespaceHandler
https\://www.hazelcast.com/schema/spring=com.hazelcast.spring.HazelcastNamespaceHandler
Böylece artık şöyle yapabiliriz. Kırmızı renkler ile spring.schemas dosyasında belirtilen isim kullanılıyor.
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:hz="http://www.hazelcast.com/schema/spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                http://www.hazelcast.com/schema/spring
                http://www.hazelcast.com/schema/spring/hazelcast-spring.xsd">
...
  <hz:hazelcast id="instance">
    <hz:config>
        <hz:cluster-name name="dev"/>
        <hz:network port="5701" port-auto-increment="false">
            <hz:join>
                <hz:multicast enabled="false"
                    multicast-group="224.2.2.3"
                    multicast-port="54327"/>
                <hz:tcp-ip enabled="true">
                    <hz:members>10.10.1.2, 10.10.1.3</hz:members>
                </hz:tcp-ip>
            </hz:join>
        </hz:network>
        <hz:map name="map"
            backup-count="2"
            read-backup-data="true"
            merge-policy="com.hazelcast.spi.merge.PassThroughMergePolicy">
            <hz:eviction eviction-policy="NONE" size="0"/>
        </hz:map>
    </hz:config>
  </hz:hazelcast>
... 
</beans>



10 Ekim 2022 Pazartesi

SpringData QueryDSL Kullanımı

Giriş
QueryDSL dinamik sorguları kolaylaştırır.
1. Maven veya gradle dependency eklenir
2. JPA Entity tanımlanır
3.  Q Sınıfları için kod üretilir
4. QuerydslPredicateExecutor sınıfından kalıtan bir repository yaratılır veya
JPAQueryFactory direkt kullanılır

Maven
Örnek
Şöyle yaparız
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Query DSL -->
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
</dependency>
Örnek
Şöyle yaparız. Burada querydsl-apt provided olarak belirtiliyor.
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
  <version>5.0.0</version>
</dependency>
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-apt</artifactId>
  <version>5.0.0</version>
  <scope>provided</scope>
</dependency>

Maven Plugin
Örnek - JPAAnnotationProcessor
Şöyle yaparız
<plugin>
  <groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
Kod üretmek için şöyle yaparız
mvn clean compile
JPAQueryFactory Kullanımı
Örnek
Şöyle yaparız
mport com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;

@Repository
public class PersonRepository {

    @Autowired
    private EntityManager entityManager;

    public List<Person> findPeopleByLastName(String lastName) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QPerson person = QPerson.person;
        return queryFactory.selectFrom(person)
                .where(person.lastName.eq(lastName))
                .fetch();
    }
}
Test için şöyle yaparız
import com.querydsl.jpa.impl.JPAQueryFactory;

@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class PersonRepositoryTest {

  @Autowired
  private PersonRepository personRepository;

  @Autowired
  private EntityManager entityManager;

  @Test
  void testFindPeopleByLastName() {
    Person person1 = new Person();
    person1.setFirstName("John");
    person1.setLastName("Smith");
    entityManager.persist(person1);

    Person person2 = new Person();
    person2.setFirstName("Jane");
    person2.setLastName("Doe");
    entityManager.persist(person2);

    List<Person> result = personRepository.findPeopleByLastName("Smith");

    assertThat(result).hasSize(1);
    assertThat(result.get(0).getFirstName()).isEqualTo("John");
    assertThat(result.get(0).getLastName()).isEqualTo("Smith");
  }
}


QuerydslPredicateExecutor Kullanımı
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.DatePath;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.core.types.dsl.StringPath;
gibi sınıflar ile bir path yaratılır

Örnek
Bir örnek burada.

Örnek
Elimizde şöyle bir Entity olsun
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

import com.vladmihalcea.hibernate.type.json.JsonType;

import lombok.Data;

@Entity
@Table(name = "abhi_roles")
@Data
@TypeDef(name = "json", typeClass = JsonType.class)
public class Roles implements Serializable {

  private static final long serialVersionUID = 1L;
	
  @Id
  @Column(name = "employeeId", length = 10)
  private String employeeId;

  @Column(name = "username")
  private String username;

  @Type(type = "json")
  @Column(name = "roles", columnDefinition = "json")
  private List<String> roles;
}
Şöyle yaparız
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
@Repository public interface RolesRepository extends JpaRepository<Roles, String>, QuerydslPredicateExecutor<Roles> { }
Service kısmında şöyle yaparız
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;

@Service
public class RolesService {

  @Autowired
  RolesRepository repository;

  public List<Roles> findUsersWithRoles(String role) {
    QRoles userRolesPath = QRoles.roles1;
    BooleanExpression exp = Expressions
      .booleanTemplate("json_contains_key({0}, {1})", userRolesPath.roles, 
        Expressions.constant(role))
      .isTrue();
    Iterable<Roles> lr = repository.findAll(exp);
List<Roles> result = new ArrayList<>(); lr.forEach(result::add); return result; } }