28 Aralık 2020 Pazartesi

SpringTest MockMvcRequestBuilders Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
get metodu
Örnek
Şöyle yaparız
@Test
public void testGenerateStudent() throws Exception {

  Student student = new Student();
  student.setName("TestStudent");
  Mockito.when(studentService.getRandomStudent()).thenReturn(student);

  mockMvc.perform(MockMvcRequestBuilders.get("/generate-student"))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andDo(print())
    .andExpect(MockMvcResultMatchers.jsonPath("$.name").isNotEmpty());

}
multipart metodu
Örnek
Şöyle yaparız
MockMultipartFile file1 = ...
MockMultipartFile file2 = ...

MvcResult result = mvc.perform(
  MockMvcRequestBuilders.multipart("/api/files")
    .filefile1)
    .file(file2))
  .andDo(print())
  .andExpect(status().isCreated())
  .andReturn();
param metodu
@RequestParam için gereken veriyi gönderir

Örnek
Elimizde şöyle bir kod olsun
@RestController @RequiredArgsConstructor @Slf4j public class LinksController { private final LinksService linksService; @GetMapping("/validateLink-url") public UrlSafety validateLink(@RequestBody String link, @RequestParam("directSearch") boolean isDirectSearch) { ... } }
Şöyle yaparız
@WebMvcTest public class LinksControllerIntegrationTest { @Autowired private MockMvc mockMvc; @MockBean private LinksService linksService; @Test public void testValidateLinkDirectSearchTrue_WhenUrlExists_ShouldReturnValid() String url = "http://safe-url.com"; when(linksService.isSafeUrlByDirectSearch(url)).thenReturn(true); mockMvc.perform(get("/validateLink-url") .param("directSearch", "true") .content(url) .contentType(MediaType.TEXT_PLAIN) .accept(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(status().isOk()) .andExpect(content().string("\"" + UrlSafety.VALID.getValue() + "\"")); verify(linksService, times(1)).isSafeUrlByDirectSearch(url); }

post metodu
Post isteğine karşılık sonuç olarak döndürülen json'ı beklenen json ile karşılaştırmak için şöyle yaparız
@Test
void listFoods() {
  String expectedResponse = "...";
  mockMvc.perform(MockMvcRequestBuilders.post("/graphql")
    .content("{\"query\":\"{ foods { id name isGood } }\"}")
    .contentType(MediaType.APPLICATION_JSON)
    .accept(MediaType.APPLICATION_JSON))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andExpect(MockMvcResultMathers.content().json(expectedResponse))
    .andReturn();
}

27 Aralık 2020 Pazar

SpringJMS JmsListenerConfigurer Arayüzü

Giriş
Şu satırı dahil ederiz
import org.springframework.jms.annotation.JmsListenerConfigurer;
Açıklaması şöyle. Yani @JmsListener anotasyonunu kullanmak daha kolay :)
Optional interface to be implemented by a Spring managed bean willing to customize how JMS listener endpoints are configured. Typically used to define the default JmsListenerContainerFactory to use or for registering JMS endpoints in a programmatic fashion as opposed to the declarative approach of using the @JmsListener annotation.
configureJmsListeners metodu
Örnek
Elimizde şöyle bir kod olsun
@Component
public class QueueService implements MessageListener {
  @Autowired
  private JmsTemplate jmsTemplate;
  public void send(String destination, String message) {
    jmsTemplate.convertAndSend(destination, message);
  }
  @Override
  public void onMessage(Message message) {
    if (message instanceof ActiveMQTextMessage) {
      ActiveMQTextMessage textMessage = (ActiveMQTextMessage) message;
      try {
        LOGGER.info("Completed task " + textMessage.getText());
      } catch (InterruptedException | JMSException e) {
        e.printStackTrace();
      }
    } else {
      LOGGER.error("Message is not a text message " + message.toString());
    }
  }
}
Şöyle yaparız
@SpringBootApplication
@EnableJms
public class SpringBootApplication implements JmsListenerConfigurer {
  @Autowired
  private QueueService queueService;
  
  public static void main(String[] args) {
    SpringApplication.run(SpringBootApplication.class, args);
  }
  
  @Override
  public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
    SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
    endpoint.setId("myId");
    endpoint.setDestination("queueName");
    endpoint.setMessageListener(queueService);
    registrar.registerEndpoint(endpoint);
  }
}
Örnek
Şöyle yaparız
@SpringBootApplication
@EnableJms
public class SpringBootApplication implements JmsListenerConfigurer {
  @Autowired
  private QueueService queueService;
  public static void main(String[] args) {
    SpringApplication.run(SpringBootApplication.class, args);
  }

  @Override
  public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
    SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
    endpoint.setId("myId");
    endpoint.setDestination("queueName");
    endpoint.setMessageListener(queueService);
    registrar.registerEndpoint(endpoint);
  }
}

25 Aralık 2020 Cuma

SpringDoc

Giriş
Açıklaması şöyle
SpringDoc is a library for Spring Boot applications that generates Open Api Schema automatically.
Örnek
Şöyle bir istek gönderelim
GET /person/{id}
Çıktı olarak şunu alırız
"PersonDTO": {
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "format": "int64"
    },
    "firstName": {
      "type": "string"
    },
    "lastName": {
      "type": "string"
    }
  }
}

24 Aralık 2020 Perşembe

SpringBoot spring.jpa Hibernate'e Özel Ayarlar - SQL Loglama Ayarları

Giriş
Sadece Spring kullanarak sql loglama biraz yetersiz kaliyor

1. Stdout'a Loglama
show-sql Alanı
Açıklaması şöyle. Yani çıktıyı System.out'a gönderir, ancak System.out yavaştır ve loglanmaz
It’s pretty common for developers to enable the spring.jpa.show-sql setting in the configuration file. By setting this to true, we will see all SQL statements performed by Hibernate printed on the console. This is very helpful for debugging performance issues, as we can see exactly what’s going on in the database.

But it doesn’t log the SQL query. It prints it on the console!
Örnek
Şöyle yaparız.
spring.jpa.show-sql=true
Şuna benzer bir çıktı alırız. Ancak bu çıktı uzun SQL cümleleri için okunaklı değil.
Hibernate: insert into program (id, created_at, title, image_url) values (?, ?, ?, ?)
2. Daha  Detaylı Loglama
1. Daha okunaklı bir SQL isteyebiliriz
2. Ancak yine de bir sorun var. spring.jpa.show-sql=true parametreleri göstermiyor. Parametreleri görmek isteyebiliriz

2.1 Daha Okunaklı SQL
Eğer Hibernate kullanıyorsak daha okunaklı SQL cümleleri için şöyle yaparız
spring.jpa.properties.hibernate.format_sql=true
Şuna benzer bir çıktı alırız. Bu çıktı biraz daha okunaklı ancak halen parametreler görünmüyor
Hibernate: 
    insert 
    into
        program
        (id, created_at, title, image_url) 
    values
        (?, ?, ?, ?)
2.2 Parametreleri görmek
Açıklaması şöyle
By using loggers, we can trace the statement parameters also.
Açıklaması şöyle
Hibernate uses 2 different log categories and log levels to log the executed SQL statements and their bind parameters:

1. The SQL statements are written as DEBUG messages to the category org.hibernate.SQL.
2. The bind parameters are logged to the org.hibernate.type.descriptor.sql category with log level 
TRACE.
Örnek
Örnek
Şöyle yaparız. Burada logger üzerinden ayar yapılıyor
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
Şuna benzer bir çıktı alırız. ? karakteri halen var ancak en azından parametreler de loglanıyor.
....SQL : insert into program (id, created_at, title, image_url) values (?, ?, ?, ?)
...BasicBinder : binding parameter [1] as [BIGINT] — [1]
...BasicBinder : binding parameter [2] as [TIMESTAMP] — [Sat Oct 24 00:28:25 IST 2020]
...BasicBinder : binding parameter [3] as [VARCHAR] — [Hello World]
...BasicBinder : binding parameter [4] as [VARCHAR] — [hello.jpg]
Örnek
Şöyle yaparız. Burada ayarlar Hibernate kütüphanesine geçiliyor.
spring.jpa.properties.hibernate.type=trace

3. Yavaş SQL Cümleleri
Açıklaması şöyle
Once you’ve enabled query logging, you can analyze the logs to identify slow queries. Slow queries are typically defined as queries that take longer than a certain threshold to execute. This threshold can be set based on your application’s performance requirements.

In the logs, you’ll see the SQL statements that were executed along with the time it took to execute each statement. You can use a log analyzers tool, like Log Analyzer or Log Analyzer 2, to analyze the logs and identify slow queries.
Örnek
Şöyle yaparız
# Enables logging
logging.level.org.hibernate.type=trace
logging.level.org.hibernate.stat=debug
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace

# Log slow queries (if query execution time exceeds the specified value in milliseconds)
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.session_factory.statistics.log_summary=true
spring.jpa.properties.hibernate.session_factory.statistics.log_slow_statements=true
spring.jpa.properties.hibernate.session_factory.statistics.slow_query_threshold_millis=500





SpringCloud Feign @FeignClient Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.netflix.feign.FeignClient;
configuration Alanı
Örnek - Basic Authentication
Şöyle yaparız
import feign.auth.BasicAuthRequestInterceptor;

public class ZephyrFeignClientConfiguration {

  @Value("${zephyr.api.username}")
  private String jiraApiUsername;

  @Value("${zephyr.api.password}")
  private String jiraApiPassword;

  @Bean
  public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor(jiraApiUsername, jiraApiPassword);
  }
}

@FeignClient(name = "...", url = "...", 
  configuration = ZephyrFeignClientConfiguration.class)
public interface ZephyrFeignClient {

  @RequestMapping(value = "/execution", method = RequestMethod.GET)
  ExecutionListResponseDto getListOfExecutions(@RequestParam(name = "issueId") 
    String issueId);

}
Açıklaması şöyle
Notice — do not mark the whole class as @Configuration. This would make the BasicAuthRequestInterceptor bean available in the whole Spring context and therefore it would be picked up by other Feign Clients.
Örnek - Token Authentication
Şöyle yaparız
import feign.RequestInterceptor;
import feign.RequestTemplate;

public class SlackFeignClientConfiguration {

  @Value("${slack.app.oauth.accessToken}")
  private String slackOauthAccessToken;

  @Bean
  public RequestInterceptor bearerTokenRequestInterceptor() {
    return new RequestInterceptor() {
      @Override
      public void apply(RequestTemplate template) {
        template.header("Authorization",
                        String.format("Bearer %s", slackOauthAccessToken));
      }
    };
  }
}

@FeignClient(name = "Slack", url = "...", 
  configuration = SlackFeignClientConfiguration.class)
public interface SlackFeignClient {

  @RequestMapping(
    value = "/chat.postMessage",
    method = RequestMethod.POST,
    consumes = "application/json",
    produces = "application/json")
  SlackMessageResponseDto postSlackMessage(@RequestBody(required = true) 
    SlackMessageRequestDto messageRequest);
}
fallback Alanı
Bu alanın çalışması için application.properties dosyasına şu eklenir. Böylece servis çalışmıyorsa fallback metodu çağrılır
feign.circuitbreaker.enabled=true
Örnek
Şöyle yaparız
@FeignClient(name="score-segment", fallback = ScoreSegmentFallback.class)
public interface ScoreSegmentProxy {

    @GetMapping("/score-segment/{idNumber}")
    public ScoreSegmentResponse retrieveExchangeValue(@PathVariable BigInteger idNumber);
}

@Component
public class ScoreSegmentFallback implements ScoreSegmentProxy {

  @Override
  public ScoreSegmentResponse retrieveExchangeValue(BigInteger idNumber) {
    return new ScoreSegmentResponse(BigInteger.ONE);
  }
}
name Alanı
url belirtilmediği için Eureka sunucusundan servisi bulur

Örnek
Şöyle yaparız
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.EmployeeDashBoardService.domain.model.EmployeeInfo;

@FeignClient(name="EmployeeSearch" )//Service Id of EmployeeSerach service
public interface EmployeeServiceProxy {

   @RequestMapping("/employee/find/{id}")
   public EmployeeInfo findById(@PathVariable(value="id") Long id);

   @RequestMapping("/employee/findall")
   public Collection<EmployeeInfo> findAll();

}
Açıklaması şöyle
Feign dynamically generates the implementation of the interface we created, so Feign has to know which service to call beforehand. That's why we need to give a name for the interface, which is the {Service-Id} of EmployeeService. Now, Feign contacts the Eureka server with this Service Id, resolves the actual IP/hostname of the EmployeeService, and calls the URL provided in Request Mapping.
url Alanı
Eğer Eureka kullanmıyorsak, URL ile adresi belirtmek gerekir.

Örnek - application.properties
application.properties şöyle olsun
zephyr:
  api:
    baseUrl: https://jira.my-company.com/jira/rest/zapi/latest
Şöyle yaparız
@FeignClient(name = "Zephyr", url = "${zephyr.api.baseUrl}", 
  configuration = ZephyrFeignClientConfiguration.class)
public interface ZephyrFeignClient {

  @RequestMapping(value = "/execution", method = RequestMethod.GET)
  ExecutionListResponseDto getListOfExecutions(@RequestParam(name = "issueId") 
    String issueId);

}

Örnek - dynamic URL
Şöyle yaparız
import java.net.URI;

@FeignClient(name = "exchangeratelistfeignservice",url = "https://dummy.com")
public interface ExchangeRateListFeignService {
  @GetMapping("")
  ResponseEntity<String> getAllExchangeRates(URI baseUrl);
}

@GetMapping Örnekleri
Örnek
Şöyle yaparız
@FeignClient(name = "jsonplaceholder", url = "https://...")
public interface PostClient {
  @GetMapping
  List<PostDTO> getPosts();
}
Örnek
Şöyle yaparız
@FeignClient(url = "<host:port for serviceB>", name = "serviceB")
public interface ServiceBClient{
  @GetMapping("/hello")
  public ResponseEntity<String> sayHello();
}
Örnek - @PathVariable İle Parametre
Şöyle yaparız
@FeignClient(name = "products", url = "https://dummyjson.com/products")
public interface ProductClient {

  @GetMapping("/{id}")
  Product fetchProduct(@PathVariable int id);
}
@RequestMapping Örnekleri

Örnek - @RequestMapping ile Get
Şöyle yaparız
@FeignClient(name = “external-service”, url = “${external-service.url}”,
configuration = ServiceConfiguration.class) 
public interface ExternalServiceClient
 
  @RequestMapping(method = RequestMethod.GET, value = “/external- data/{time}”,
consumes = “application/json”
  Data load(@PathVariable(“time”) Long time); 
}
Örnek
Şöyle yaparız
@FeignClient(name = "bonus", url = "http://localhost:8081/bonus")
public interface BonusClient {

  @RequestMapping(value = "/register", method = RequestMethod.POST)
  String register();
}

@FeignClient(name = "notification", url = "http://localhost:8082/notification")
public interface NotificationClient {

  @PostMapping("/send")
  String send(@RequestBody NotificationRequest notificationRequest);
}

@RestController
@RequestMapping("/bonus")
public class BonusController {

  @PostMapping("/register")
  String register() {
    ...
  }
}

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

   @PostMapping("/send")
   String send(@RequestBody NotificationRequest notificationRequest) {
     ...
  }
}
Örnek - RequestMapping + BasicAuthRequestInterceptor
BasicAuthentication kullanarak Get isteği göndermek için şöyle yaparız. Burada Spring MVC'ye aiıt
@DeleteMapping
@GetMapping
@PostMapping
@PutMapping
kullanılabilir.

Parametre geçmek için 
@PathVariable
@RequestHeader
@RequestParam
kullanılabilir.
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(name = "Zephyr", url = "${zephyr.api.baseUrl}",
configuration = ZephyrFeignClientConfiguration.class)
public interface ZephyrFeignClient {

  @RequestMapping(value = "/execution",method = RequestMethod.GET)
  Foo getListOfExecutions(@RequestParam(name = "issueId") String issueId);

}
Burada url değeri application.yml dosyasında şöyle tanımlı
zephyr:
api: baseUrl: https://jira.my-company.com/jira/rest/zapi/latest
Şöyle yaparız
import feign.auth.BasicAuthRequestInterceptor;

public class ZephyrFeignClientConfiguration {

  @Value("${zephyr.api.username}")
  private String jiraApiUsername;

  @Value("${zephyr.api.password}")
  private String jiraApiPassword;

  @Bean
  public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor(jiraApiUsername, jiraApiPassword);
  }

}
Kullanmak için şöyle yaparız
@Service
@RequiredArgsConstructor
public class MyService {

  private final ZephyrFeignClient zephyrClient;

  public void fetchData() {
    Foo foo = zephyrClient.getListOfExecutions("5112096");
    ...
  }
}

Docker ve SpringBoot

Giriş
SpringBoot uygulamasını Docker image haline dönüştürmek için yöntemler şöyle
1. spring-boot-maven-plugin kullanmak. Bu plugin ile Dockerfile yazmaya gerek yok
2. Jib kullanmak
3. Dockerfile yazmak

1. Jib Plugin kullanmak
Maven için jib plugin yazısına taşıdım
Gradle için jib plugin yazısına taşıdım


2. Dockerfile yazmak
Açıklaması şöyle
This is the traditional approach where a fat uber jar containing the java artifacts (spring boot libraries, dependency libraries, class files) are packaged into a single jar file using the mvn package command. The developer needs to create the Dockerfile. 
Örnek - JDK
Şöyle yaparız
FROM adoptopenjdk/openjdk11
EXPOSE 8080
ARG JAR_FILE=target/rest-service-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Açıklaması şöyle. Üretilen Docker image biraz büyük oluyor
The developer must first run the mvn package command to create the reference jar file target/rest-service-0.0.1-SNAPSHOT.jar that is the uber jar fille containing everything to run the microservice.

To build the Docker image run the command:
docker build -t rest-server-dockerfile:0.0.1 .

Command to run the image:
docker run -it -p8080:8080 rest-server-dockerfile:0.0.1

This approach is not the most efficient. The image size is relatively big at 455 MB. The reason why it is big is because it uses the adoptopenjdk/openjdk11 base image. A jre image as opposed to a jdk image will result in a smaller container image size. Another reason why this approach is not efficient is because the resulting Docker image has only 4 layers, where one of the layers consists of the uber jar. The line in the Dockerfile that adds the uber jar file layer to the Docker image is ADD ${JAR_FILE} app.jar. Therefore, when a single java file changes, the java application needs to be rebuilt (all spring boot libraries, dependencies, app source files), a new fat jar needs to be created and then a new image needs to be created with the new fat jar layer.
Eğer JDK yerine JRE kullanmak istesek şöyle yaparız
FROM adoptopenjdk:11-jre-hotspot
EXPOSE 8080
ARG JAR_FILE=target/rest-service-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Örnek - JDK Yöntemi
Şöyle yaparız. Burada bir fat jar var ve sunucumuzun application.properties dosyasında 8443 numaralı portu kullanacağı yazıyor. Böylece farklı container'lar bize bu porttan ulaşabilir.
From openjdk:12-jdk-alpine
COPY build/libs/my-spring-boot-app-0.1.0.jar /usr/app
WORKDIR /usr/app
RUN sh -c 'touch my-spring-boot-app-0.1.0.jar'
EXPOSE 8443
ENTRYPOINT ["java","-jar","my-spring-boot-app-0.1.0.jar"] 
Docker imajı yaratmak ve çalıştırmak için şöyle yaparız
docker build . -t mysecurespringbootapp
docker run -d --name secureapp -p 8443:8443 mysecurespringbootapp:latest
Uygulamamızın loglarını görmek için şöyle yaparız
docker logs -f secureapp
3. Multi-staged Docker