Weblux swagger pomocí springfox

Swagger je nástroj (knihovna), který umožňuje dokumentovat REST endpointy vaší aplikace. Dobré je to například pro vývoj, kdy konzument (např. vývojář frontendu) ví, jaké jsou aktuálně k dispozici endpointy, jaké mají vstupy a jaké objekty vrací. Jak zprovoznit swagger v projektu, který používá WebFlux a Spring Boot ukáži v tomto příspěvku.

Pokud máte projekt a používáte pro restové služby framework WebFlux a Spring Boot, je zprovoznění Swaggeru jednoduché. Stačí přidat následující závislosti (používám Gradle a Kotlin):

implementation("io.springfox:springfox-swagger2:3.0.0-SNAPSHOT")
implementation("io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT")
implementation("io.springfox:springfox-spring-webflux:3.0.0-SNAPSHOT")

a vytvořit konfigurační třídu pro Swagger:

Číst dálWeblux swagger pomocí springfox

Zakázání basic auth pro některé endpointy ve Spring OAuth2

To, že Spring OAuth2 řadu endpointů generuje, jsem se zmínil v jednom z předchozích příspěvků. Na jednu stranu je to výhoda, protože většinu věcí máte hotovou, na stranu druhou to může v některých případech být problém. Například můžete chtít, aby endpoint PUT /oauth/token neměl authentikaci. Normálně byste napsali něco následujícího:

override fun configure(http: HttpSecurity) {
    http
            .authorizeRequests()
            .antMatchers(HttpMethod.PUT, "/oauth/token").permitAll()
            .anyRequest().denyAll()
            .and().csrf().disable()
            .formLogin().disable()
}

To ale v tomto případě nestačí, protože request zachytí AuthorizationServerSecurityConfiguration. Ta má nastavený @Order na 0. Řešením je, ve třídě která dědí od WebSecurityConfigurerAdapter nastavit @Order na -1 (@Order(-1)) a přetížit metodu configure následujícím způsobem.

override fun configure(http: HttpSecurity) {
    http
            .requestMatchers()
            .antMatchers(HttpMethod.PUT, "/oauth/token")
            .and().authorizeRequests().anyRequest().permitAll()
            .and().csrf().disable()
            .formLogin().disable()
}

Nezapomeňte na csfr().disable(), jinak budete dostávat 403.

Spring OAuth2

Pro vytvoření oauth autorizace nabízí spring knihovnu

org.springframework.security.oauth:spring-security-oauth

Zde najdete celkem 8 endpointů označených anotací @FrameworkEndpoint:

  • /oauth/authorize (class AuthorizationEndpoint)
  • /oauth/authorize, method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL (class AuthorizationEndpoint)
  • /oauth/check_token (class CheckTokenEndpoint)
  • /oauth/token, method=RequestMethod.GET (class TokenEndpoint)
  • /oauth/token, method=RequestMethod.POST (class TokenEndpoint)
  • /oauth/token_key, method = RequestMethod.GET (class TokenKeyEndpoint)
  • /oauth/confirm_access (class WhitelabelApprovalEndpoint)
  • /oauth/error (class WhitelabelErrorEndpoint)

Anotace @FrameworkEndpoint znamená, že tyto endpointy jsou vytvořeny frameworkem. Nemusíte je tedy vytvářet sami.

Spring, Kotlin a JSON decoding errors

Nedávno jsem narazil na zajímavý problém. Měl jsem dvě služby, kdy se přes REST rozhraní zavolá jedna, ta provolá druhou a vrátí výsledek. Původní odpověď vypadala takto:

data class GetAchievementsResponse(
        achievements: List<AchievementInfo>
)

Jakmile jsem ale objekt změnil takto:

data class GetAchievementsResponse(
        val aAchievements: List<AchievementInfo>,
        val rAchievements: List<AchievementInfo>
)

Začal jsem dostávat tyto chyby:

JSON decoding error: Instantiation of [simple type, class cz.vitfo.client.GetAchievementsResponse] value failed for JSON property aAchievements due to missing (therefore NULL) value for creator parameter aAchievements which is a non-nullable type
JSON decoding error: Cannot deserialize instance of `java.util.ArrayList` out of FIELD_NAME token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of FIELD_NAME token

Nakonec jsem přišel na to, že z nějakého důvodu com.fasterxml.jackson v tomto případě nezvládá camel case názvy proměnných. Řešením bylo toto pojmenování:

data class GetAchievementsResponse(
        val a_achievements: List<AchievementInfo>,
        val r_achievements: List<AchievementInfo>
)

Vrácení pouze některých sloupců při vytvoření dotazu pomocí entity manažera

Pokud vytváříte dotaz pomocí entity manažera (viz příspěvek Spring Data a vytvárení dotazů pomocí entiy managera) můžete omezit sloupce, které daným dotazem budete vracet. V tomto případě používám tabulku t_order se sloupci id, identifier a sum, která je v představována entitou Order.

Číst dálVrácení pouze některých sloupců při vytvoření dotazu pomocí entity manažera

Spring Data a získání metod pro základní operace nad tabulkou

Spring Data poskytuje rozhraní CrudRepository<T, ID>, takže pokud chceme získávat data z databázové tabulky, stačí nám vytvořit vlastní rozhraní, který bude rozšiřovat CrudRepository a určit typ entity, kterou tabulka obsahuje. Jednoduchý příklad to objasní. Pokud budeme mít entitu Order

import java.math.BigDecimal
import java.util.*
import javax.persistence.*

@Entity
@Table(name = "t_order")
@SequenceGenerator(name = "t_order_seq_gen", sequenceName = "t_order_seq", allocationSize = 1)
class Order(

        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "t_order_seq_gen")
        var id: Long = 0L,

        var identifier: UUID = UUID.randomUUID(),

        var sum: BigDecimal = BigDecimal.ZERO
)

vytvoří se nám (volba spring.jpa.generate-ddl: true v application.properties) tabulka t_order. Pro operace s daty této tabulky vytvoříme rozhraní OrderRepository, které bude rozšiřovat CrudRepository.

import cz.vitfo.springdata.entity.Order
import org.springframework.data.repository.CrudRepository

interface OrderRepository : CrudRepository<Order, Long>

Toto stačí, abychom měli k dispozici sadu základních metod (count, delete, deleteAll, deleteById, existsById, findAll, findAllById, findById, save, saveAll) a další metody mohli jednoduše vytvářet použitím jmenné konvence – findByUuid, findByUuidAndSum, …

Spring Data a vytváření dotazů pomocí entity manažera

To jak Spring Data zjednodušuje práci s databází jsem ukázal v příspěvku Spring Data a získání metod pro základní operace nad tabulkou. V tomto příspěvku si ale ukážeme jak vytvářet sql příkazy pomocí entity manažera (EntityManager).

Vytvoříme si třídu s názvem OrderRepository a označíme ji jako repository anotací @Repository (org.springframework.stereotype.Repository).

Číst dálSpring Data a vytváření dotazů pomocí entity manažera

Spuštění kódu při startu Springu

Pokud potřebujete spusti určitý kód po naběhnutí Springu, můžete použít CommandLineRunner. Je to rozhraní, které indikuje beanu, která má být spuštěna jakmile je ve Spring aplikaci.

Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or Order @Order annotation.

Zde je jednoduchý příklad, kdy si vytvoříme konfigurační třídu s názvem SpringDataConfig.

Číst dálSpuštění kódu při startu Springu

Spring Data a Liquibase

Liquibase umožňuje hlídat změny v databázi. Každá změna (blok změn) má svůj vlastní changeset. Liquibase si vytváří dvě vlastní tabulky: databasechangelog, kde jsou uloženy provedené changesety a databasechangeloglock, která slouží pro zamykání (řeší případ přístupu, kdy více vývojářů dělá změny v databázi současně). Může se stát, že pokud Liquibase neskončí korektně a zůstane zámek. Pak je potřeba locked nastavit na false manuálně.

Číst dálSpring Data a Liquibase

Vytvoření tabulek pro Spring Batch

Spring Batch používá 6 tabulek, pro ukládání dat:

batch_job_execution
batch_job_execution_context
batch_job_execution_params
batch_job_instance
batch_step_execution
batch_step_execution_context

Tyto tabulky vytvoříte přidáním následujícího řádku do souboru application.properties:

spring.batch.initialize-schema=always

Zdroj: stackoverflow.com/…-auto-create-batch-table