Skip to content

Latest commit

 

History

History
4877 lines (3482 loc) · 104 KB

javax-mcr-slides.md

File metadata and controls

4877 lines (3482 loc) · 104 KB

class: inverse, center, middle

Microservice alkalmazás felépítése Spring Boot keretrendszerrel Docker környezetben


class: inverse, center, middle

Bevezetés


Referenciák


class: inverse, center, middle

Bevezetés a Spring Framework használatába


Spring Framework céljai

  • Keretrendszer nagyvállalati alkalmazásfejlesztésre
  • Keretrendszer: komponensek hívása, életciklusa
  • Nagyvállalati alkalmazás: Java SE által nem támogatott tulajdonságok
    • Spring Framework nem magában ad választ, hanem integrál egy vagy
      több megoldást

Nagyvállalati alkalmazás - 1.

  • Komponensek életciklus kezelése és kapcsolatok
  • Távoli elérés
  • Többszálúság
  • Perzisztencia
  • Tranzakció-kezelés
  • Aszinkron üzenetkezelés
  • Ütemezés

Nagyvállalati alkalmazás - 2.

  • Integráció
  • Auditálhatóság
  • Konfigurálhatóság
  • Naplózás, monitorozás és beavatkozás
  • Biztonság
  • Tesztelhetőség

Spring Framework
tulajdonságai - 1.

  • Komponensek, melyeket konténerként tartalmaz
    (konténer: application context)
  • Konténer vezérli a komponensek életciklusát
    (pl. példányosítás)
  • Konténer felügyeli a komponensek közötti kapcsolatot
    (Dependency Injection, Inversion of Control)
  • Komponensek és kapcsolataik leírása több módon:
    XML, annotáció, Java kód
  • Pehelysúlyú, non invasive (POJO komponensek)
  • Aspektusorientált programozás támogatása

Spring Framework
tulajdonságai - 2.

  • 3rd party library-k integrálása az egységes modellbe
  • Glue kód
  • Boilerplate kódok eliminálása
  • Fejlesztők az üzleti problémák
    megoldására koncentráljanak

Application Context

Application Context


Háromrétegű webes alkalmazás

  • Nem kizárólag erre, de ez a fő felhasználási terület
  • Rétegek
    • Repository
    • Service
    • Controller
  • Hangsúlyos része a Spring MVC webes alkalmazások írására,
    HTTP felé egy absztrakció
  • HTTP kezelését web konténerre bízza,
    pl. Tomcat, Jetty, stb.

Rétegek

Rétegek


class: inverse, center, middle

Bevezetés a Spring Boot használatába


Spring Boot - 1.

  • Autoconfiguration: classpath-on lévő osztályok, 3rd party library-k, környezeti változók és egyéb körülmények alapján komponensek automatikus
    létrehozása és konfigurálása
  • Intelligens alapértékek, convention over configuration,
    konfiguráció nélkül is működjön
    • Saját konfiguráció írása csak akkor, ha eltérnénk az alapértelmezettől
    • Automatically, automagically
  • Self-contained: az alkalmazás tartalmazza
    a web konténert is (pl. Tomcat)

Spring Boot - 2.

  • Nagyvállalati üzemeltethetőség: Actuatorok
    • Pl. monitorozás, konfiguráció, beavatkozás,
      naplózás állítása, stb.
  • Gyors kezdés: Spring Initializr https://start.spring.io/
  • Starter projektek: függőségek,
    előre beállított verziószámokkal (tesztelt)
  • Ezért különösen alkalmas microservice-ek fejlesztésére

Könyvtárstruktúra

├── .gitignore
├── .mvn
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── training
    │   │       └── employees
    │   │           └── EmployeesApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── training
                └── employees
                    └── EmployeesApplicationTests.java

pom.xml

  • Parent: org.springframework.boot:spring-boot-starter-parent
    • Innen öröklődnek a függőségek, verziókkal együtt
  • Starter: org.springframework.boot:spring-boot-starter-web
    • Jackson, Tomcat
  • Teszt támogatás: org.springframework.boot:spring-boot-starter-test

class: inverse, center, middle

Spring Beanek


Spring Beanek

  • Spring bean: tud róla a Spring konténer
  • Spring példányosítja
  • Spring állítja be a függőségeit
  • POJO
  • Rétegekbe rendezve
  • Alapértelmezetten singleton, egy példányban jön létre

Application

  • Alkalmazás belépési pontja main() metódussal
  • @SpringBootApplication:
    • @EnableAutoConfiguration: autoconfiguration bekapcsolása
    • @SpringBootConfiguration: @Configuration: maga az osztály is
      tartalmazhasson további konfigurációkat
    • @ComponentScan: @Component,
      @Repository, @Service, @Controller annotációval ellátott
@SpringBootApplication
public class EmployeesApplication {

  public static void main(String[] args) {
    SpringApplication.run(EmployeesApplication.class,
      args);
  }

}

Controller komponensek

  • Spring MVC része
  • Felhasználóhoz legközelebb lévő réteg, felelős a
    felhasználóval való kapcsolattartásért
    • Adatmegjelenítés és adatbekérés
  • POJO
  • Annotációk erős használata
  • Nem feltétlenül van Servlet API függősége
  • Metódusok neve, paraméterezése flexibilis
  • Gyakran REST végpontok kialakítására használjuk

Annotációk

  • @Controller: megtalálja a component scan, Spring MVC felismeri
  • @RequestMapping milyen URL-en hallgat
    • Ant-szerű megadási mód (pl. /admin/*.html)
    • Megadható a HTTP metódus a method paraméterrel
  • @ResponseBody visszatérési értékét azonnal a HTTP válaszba kell írni
    (valamilyen szerializáció után)

Controller

@Controller
public class EmployeesController {

    @RequestMapping("/")
    @ResponseBody
    public String helloWorld() {
        return "Hello World!";
    }
}

Service

@Service
public class EmployeesService {

    public String helloWorld() {
        return "Hello World at " + LocalDateTime.now();
    }
}

Kapcsolatok

  • Dependency injection
  • Definiáljuk a függőséget, a konténer állítja be
  • Függőségek definiálása:
    • Attribútum
    • Konstruktor
    • Metódus
  • Legjobb gyakorlat: kötelező függőség konstruktorban
  • Ha csak egy konstruktor, automatikusan megtörténik
    a dependency injection
  • Egyéb esetben @Autowired annotáció

Függőség a controllerben

@Controller
public class EmployeesController {

    private EmployeesService employeesService;

    public EmployeesController(EmployeesService employeesService) {
        this.employeesService = employeesService;
    }

    @RequestMapping("/")
    @ResponseBody
    public String helloWorld() {
        return employeesService.helloWorld();
    }
}

class: inverse, center, middle

Statikus állományok


Statikus állomány

  • src/main/resources/static könyvtárban
  • Welcome Page: index.html

WebJars

  • JavaScript könyvtárak jar fájlba csomagolva
  • META-INF/resources könyvtárban
  • Hivatkozás pl.: /webjars/bootstrap/4.5.2/css/bootstrap.css
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.5.2</version>
</dependency>

Version agnostic

Hivatkozás pl.: /webjars/bootstrap/css/bootstrap.css

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator-core</artifactId>
</dependency>

class: inverse, center, middle

Konfiguráció Javaban


Java konfiguráció

  • Ekkor nem kell a @Service annotáció: non-invasive
  • @Configuration által ellátott osztályban, itt EmployeesApplication
  • Legjobb gyakorlat: saját beanek component scannel,
    3rd party library-k Java konfiggal
  • Legjobb gyakorlat: rétegenként külön @Configuration
    annotációval ellátott osztály

Java konfiguráció példa

@Bean
public EmployeesService employeesService() {
  return new EmployeesService();
}
public class EmployeesService {

    public String helloWorld() {
        return "Hello Dev Tools at " + LocalDateTime.now();
    }
}

class: inverse, center, middle

Build és futtatás Mavennel


Build parancssorból

  • Build parancssorból Mavennel
mvnw clean package
  • spring-boot-maven-plugin: átcsomagolás, beágyazza a web konténert
  • employees-0.0.1-SNAPSHOT.jar.original és
    employees-0.0.1-SNAPSHOT.jar

Futtatás parancssorból

  • Futtatás parancssorból Mavennel
mvnw spring-boot:run
  • Futtatás parancssorból
java -jar employees-0.0.1-SNAPSHOT.jar

class: inverse, center, middle

Build és futtatás Gradle használatával


Gradle használata

  • A start.spring.io támogatja a Gradle alapú projekt generálását
  • A io.spring.dependency-management Gradle plugin
    lehetővé tesz Maven-szerű függőségkezelést
    - csak a verziókat deklarálja, de a függőséget explicit kell megadni
  • A org.springframework.boot Gradle plugin
    képes a jar és war előállítására, figyelembe véve az előző plugint
  • Generál Gradle wrappert is - ha nincs Gradle telepítve az adott gépre
  • JUnit 5 függőség

Gradle taskok

gradle build

gradle -i build

gradle bootRun

A -i kapcsoló INFO szintű naplózást állít


class: inverse, center, middle

Unit és integrációs tesztek


Unit tesztelés

  • JUnit 5
  • Non invasive - POJO-ként tesztelhető
  • AssertJ, Hamcrest classpath-on
@Test
void testSayHello() {
  EmployeesService employeesService = new EmployeesService();
  assertThat(employeesService.sayHello())
    .startsWith("Hello");
}

Unit tesztelés függőséggel

  • Mockito classpath-on
@ExtendWith(MockitoExtension.class)
public class EmployeesControllerTest {

  @Mock
  EmployeesService employeesService;

  @InjectMocks
  EmployeesController employeesController;

  @Test
  void testSayHello() {
    when(employeesService.sayHello())
      .thenReturn("Hello Mock");
    assertThat(employeesController.helloWorld())
      .startsWith("Hello Mock");
  }
}

Integrációs tesztek

  • Üres teszt a konfiguráció ellenőrzésére, elindul-e az application context
  • @SpringBootTest annotáció: tartalmazza
    az @ExtendWith(SpringExtension.class) annotációt
  • Tesztesetek között cache-eli az application contextet
  • Beanek injektálhatóak az @Autowired annotációval

Integrációs tesztek példa

@SpringBootTest
class EmployeesControllerIT {

  @Autowired
  EmployeesController employeesController;

  @Test
  void testSayHello() {
    String message = employeesController
      .helloWorld();
    assertThat(message).startsWith("Hello");
  }
}

class: inverse, center, middle

Developer Tools


Developer Tools

  • Felülírt property-k (Property Defaults): pl. template cache kikapcsolása
  • Automatikus újraindítás
  • LiveReload
  • Globális, felhasználónkénti beállítások (Global Settings)
  • Távoli alkalmazáson osztály frissítése (Remote Applications)
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
</dependency>

Automatikus újraindítás

  • Ha változik valami a classpath-on
  • IDE függő
    • Eclipse-nél mentésre
    • IDEA-nál Build / Rebuild Project (Ctrl + F9 billentyűzetkombináció)
  • Két osztálybetöltő, az egyik a saját kód, másik a függőségek -
    változás esetén csak az elsőt tölti újra, függőség változása esetén
    manuálisan kell újraindítani
  • Újraindítja a web konténert is
spring.devtools.restart.poll-interval=2s
spring.devtools.restart.quiet-period=1s

LiveReload

  • Böngésző plugin szükséges hozzá
  • A Spring Boot elindít egy LiveReload szervert
  • LiveReload plugin felépít egy WebSocket kapcsolatot
  • Ha változik valami, újratöltés van
  • Pl. statikus állomány esetén (src/main/resources/static/*.html)
  • IDE függő
    • Eclipse-nél mentésre
    • IDEA-nál Build / Rebuild Project
      (Ctrl + F9 billentyűzetkombináció)

Global settings

  • $HOME/.config/spring-boot könyvtárban
    pl. spring-boot-devtools.properties állomány

Remote Applications

  • El lehet indítani becsomagolt alkalmazáson is a DevToolst
    (spring-boot-maven-plugin konfigurálásával)
  • Remote Client Applicationt kell elindítani, mely lokális gépen indul,
    és csatlakozik a távoli alkalmazáshoz
  • Remote Update: ha valami változik a classpath-on, feltölti
  • Ne használjuk éles környezetben

class: inverse, center, middle

Twelve factor app


Twelve-factor app

  • Twelve-factor app egy manifesztó, metodológia felhőbe
    telepíthető alkalmazások fejlesztésére
  • Heroku platform fejlesztőinek ajánlása
  • Előtérben a cloud, PaaS, continuous deployment
  • PaaS: elrejti az infrastruktúra részleteit
    • Pl. Google App Engine, Redhat Open Shift, Pivotal Cloud Foundry,
      Heroku, AppHarbor, Amazon AWS, stb.

Cloud native

  • Jelző olyan szervezetekre, melyek képesek az automatizálás előnyeit kihasználva
    gyorsabban megbízható és skálázható alkalmazásokat szállítani
  • Pivotal, többek között a Spring mögött álló cég
  • Előtérben a continuous delivery, DevOps, microservices
  • Alkalmazás jellemzői
    • PaaS-on fut (cloud)
    • Elastic: automatikus horizontális skálázódás

Twelve-Factor app ajánlások - 1.

  • Verziókezelés: "One codebase tracked in revision control, many deploys"
  • Függőségek: "Explicitly declare and isolate dependencies"
  • Konfiguráció: "Store config in the environment"
  • Háttérszolgáltatások: "Treat backing services as attached resources"
  • Build, release, futtatás: "Strictly separate build and run stages"
  • Folyamatok: "Execute the app as one or more stateless processes"

Twelve-Factor app ajánlások - 2.

  • Port hozzárendelés: "Export services via port binding"
  • Párhuzamosság: "Scale out via the process model"
  • Disposability: "Maximize robustness with fast startup and graceful shutdown"
  • Éles és fejlesztői környezet hasonlósága:
    "Keep development, staging, and production as similar as possible"
  • Naplózás: "Treat logs as event streams"
  • Felügyeleti folyamatok:
    "Run admin/management tasks as one-off processes"

Beyond the Twelve-Factor App - 1.

  • One Codebase, One Application
  • API first
  • Dependency Management
  • Design, Build, Release, Run
  • Configuration, Credentials and Code
  • Logs
  • Disposability
  • Backing services

Beyond the Twelve-Factor App - 2.

  • Environment Parity
  • Administrative Processes
  • Port Binding
  • Stateless Processes
  • Concurrency
  • Telemetry
  • Authentication and Authorization

class: inverse, center, middle

Bevezetés a Docker használatába


Docker

  • Operációs rendszer szintű virtualizáció
  • Jól elkülönített környezetek, saját fájlrendszerrel és telepített szoftverekkel
  • Jól meghatározott módon kommunikálhatnak egymással
  • Kevésbé erőforrásigényes, mint a virtualizáció

Megvalósítása

  • Kliens - szerver architektúra, REST API
  • Kernelt nem tartalmaz, hanem a host Linux kernel izoláltan futtatja
  • namespaces: operációs rendszer szintű elemek izolálására: folyamatok, InterProcess Communication (IPC), fájlrendszer, hálózat, UTS (UNIX Timesharing System - host- és domainnév), felhasználók
  • cGroups (Control Groups): erőforrás limitáció
  • Union FS (írásvédett, vagy írható/olvasható rétegek)

Docker

Docker


Docker Windowson

  • Docker Toolbox: VirtualBoxon futó Linuxon
  • Docker Desktop
    • Hyper-V megoldás: LinuxKit, Linux Containers for Windows (LCOW), MobyVM
    • WSL2 - Windows Subsystem for Linux - 2-es verziótól Microsoft által Windowson fordított és futtatott Linux kernel

Docker felhasználási módja

  • Saját fejlesztői környezetben reprodukálható erőforrások
    • Adatbázis (relációs/NoSQL), cache, kapcsolódó rendszerek
      (kifejezetten microservice környezetben)
  • Saját fejlesztői környezettől való izoláció
  • Docker image tartalmazza a teljes környezetet, függőségeket is
  • Portabilitás (különböző környezeten futattható, pl. saját gép,
    privát vagy publikus felhő)

További Docker komponensek

  • Docker Hub - publikus szolgáltatás image-ek megosztására
  • Docker Compose - több konténer egyidejű kezelése
  • Docker Swarm - natív cluster támogatás
  • Docker Machine - távoli Docker környezetek üzemeltetéséhez

Docker fogalmak

Image és container


Docker folyamat

  • Alkalmazás
  • Dockerfile
  • Image
  • Konténer

Docker konténerek

docker version
docker run hello-world
docker run -p 8080:80 nginx
docker run -d -p 8080:80 nginx
docker ps
docker stop 517e15770697
docker run -d -p 8080:80 --name nginx nginx
docker stop nginx
docker ps -a
docker start nginx
docker logs -f nginx
docker stop nginx
docker rm nginx

Használható az azonosító első n karaktere is, amely egyedivé teszi


Műveletek image-ekkel

docker images
docker rmi nginx

Linux elindítása, bejelentkezés

docker run  --name myubuntu -d ubuntu tail -f /dev/null
docker exec -it myubuntu bash

class: inverse, center, middle

Java alkalmazások Dockerrel


Saját image összeállítása

Dockerfile fájl tartalma:

FROM eclipse-temurin:17
WORKDIR app
COPY target/*.jar employees.jar
ENTRYPOINT ["java", "-jar", "employees.jar"]

Parancsok:

docker build -t employees .
docker run -d -p 8080:8080 employees

docker-maven-plugin


Plugin

<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.32.0</version>
    <!-- ... -->    
</plugin>

Plugin konfiguráció

.small-code-14[

<configuration>
    <verbose>true</verbose>
    <images>
        <image>
            <name>employees</name>
            <build>
                <dockerFileDir>${project.basedir}/src/main/docker/</dockerFileDir>
                <assembly>
                    <descriptorRef>artifact</descriptorRef>
                </assembly>
                <tags>
                    <tag>latest</tag>
                    <tag>${project.version}</tag>
                </tags>
            </build>
            <run>
                <ports>8080:8080</ports>
            </run>
        </image>
    </images>
</configuration>

]


Dockerfile

FROM eclipse-temurin:17
WORKDIR app
COPY maven/${project.artifactId}-${project.version}.jar employees.jar
ENTRYPOINT ["java", "-jar", "employees.jar"]

Property placeholder


Parancsok

mvnw package docker:build
mvnw docker:start
mvnw docker:stop

A docker:stop törli is a konténert


12Factor hivatkozás:
Disposability

  • Nagyon gyorsan induljanak és álljanak le
  • Graceful shutdown
  • Ne legyen inkonzisztens adat
  • Batch folyamatoknál: megszakíthatóvá, újraindíthatóvá (reentrant)
    • Tranzakciókezeléssel
    • Idempotencia

class: inverse, center, middle

Docker layers


Layers

Docker layers

docker image inspect employees


Legjobb gyakorlat

  • Külön változó részeket külön layerbe tenni
  • Operációs rendszer, JDK, libraries, alkalmazás saját fejlesztésű része külön
    layerbe kerüljön

Manuálisan

  • Jar fájlt ki kell csomagolni, úgy is futtatható
    • BOOT-INF/lib - függőségek
    • META-INF - leíró állományok
    • BOOT-INF/classes - alkalmazás saját fájljai
java -cp BOOT-INF\classes;BOOT-INF\lib\* training.employees.EmployeesApplication

Dockerfile

FROM eclipse-temurin:17 as builder
WORKDIR app
COPY target/*.jar employees.jar
RUN jar xvf employees.jar

FROM eclipse-temurin:17
WORKDIR app
COPY --from=builder app/BOOT-INF/lib lib
COPY --from=builder app/META-INF META-INF
COPY --from=builder app/BOOT-INF/classes classes

ENTRYPOINT ["java", "-cp", "classes:lib/*", \
            	"training.employees.EmployeesApplication"]

Spring támogatás

  • Spring 2.3.0.M2-től
  • Layered JAR-s
  • Buildpacks

Layered JAR-s

  • A JAR felépítése legyen layered
  • Ki kell csomagolni
  • Létrehozni a Docker image-t

Layered JAR

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration>
</plugin>

Kicsomagolás

java -Djarmode=layertools -jar target/employees-0.0.1-SNAPSHOT.jar list

java -Djarmode=layertools -jar target/employees-0.0.1-SNAPSHOT.jar extract

Dockerfile

FROM eclipse-temurin:17 as builder
WORKDIR app
COPY target/*.jar employees.jar
RUN java -Djarmode=layertools -jar employees.jar extract

FROM eclipse-temurin:17
WORKDIR app
COPY --from=builder app/dependencies/ ./
COPY --from=builder app/spring-boot-loader/ ./
COPY --from=builder app/snapshot-dependencies/ ./
COPY --from=builder app/application/ ./
ENTRYPOINT ["java", \
  "org.springframework.boot.loader.JarLauncher"]

Buildpacks

  • Dockerfile-hoz képest magasabb absztrakciós szint (Cloud Foundry vagy Heroku)
  • Image készítése közvetlen Maven-ből vagy Grade-ből
  • Alapesetben Java 11, spring-boot-maven-plugin konfigurálandó
    BP_JAVA_VERSION értéke 13.0.2
mvnw spring-boot:build-image
docker run -d -p 8080:8080 --name employees employees:0.0.1-SNAPSHOT
docker logs -f employees

12Factor hivatkozás:
Dependencies

  • Az alkalmazás nem függhet az őt futtató környezetre telepített
    semmilyen csomagtól
  • Függőségeket explicit deklarálni kell
  • Nem a függőségek közé soroljuk a háttérszolgáltatásokat,
    mint pl. adatbázis, mail szerver, cache szerver, stb.
  • Docker és Maven/Gradle segít
  • Egybe kell csomagolni a függőségekkel,
    hiszen a futtató környezetben szükség van rá
  • Függőségek ritkábban változnak: Docker layers
  • Vigyázni az ismételhetőségre: ne használjunk
    intervallumokat!

class: inverse, center, middle

Feltöltés Git repository-ba


Feltöltés Git repository-ba

  • Új GitHub repository létrehozás a webes felületen
git init
git add .
git commit -m "First commit"
git remote add origin https://github.com/username/employees.git
git push -u origin master

12Factor hivatkozás:
One Codebase, One Application

  • Egy alkalmazás, egy repository
  • A többi függőségként definiálandó
  • Gyakori megsértés:
    • Modularizált fejlesztésnél tűnhet ez jó ötletnek a modulokat
      külön repository-ban tartani: nagyon megbonyolítja a buildet
    • Külön repository, de ugyanazon üzleti
      domainen dolgozó különböző alkalmazás darabok
    • Egy repository, különböző alkalmazások
  • A különböző környezetekre telepített példányoknál
    alapvető igény, hogy tudjuk,
    hogy mely verzióból készült
    (felületen, logban látható legyen)

Alkalmazások és csapatok
kapcsolata

  • Figyelni a Conway törvényre: "azok a szervezetek, amelyek
    rendszereket terveznek, ... kénytelenek olyan terveket készíteni,
    amelyek saját kommunikációs struktúrájuk másolatai"
  • Egy codebase, több team - ellentmond a microservice elképzelésnek
  • Lehetséges megosztás:
    • Library - függőségként felvenni
    • Microservice

class: inverse, center, middle

REST webszolgáltatások GET művelet


RESTful webszolgáltatások tulajdonságai

  • Roy Fielding: Architectural Styles and the Design of Network-based
    Software Architectures, 2000
  • Representational state transfer
  • Alkalmazás erőforrások gyűjteménye,
    melyeken CRUD műveleteket lehet végezni
  • HTTP protokoll erőteljes használata:
    URI, metódusok, státuszkódok
  • JSON formátum használata
  • Egyszerűség, skálázhatóság, platformfüggetlenség
  • Richardson Maturity Model

Annotációk

  • @RestController, mintha minden metóduson @ResponseBody annotáció
    • Alapértelmezetten JSON formátumba
  • @RequestMapping annotation helyett @GetMapping, @PostMapping, stb.

Controller

@RestController
@RequestMapping("/api/employees")
public class EmployeesController {

    private final EmployeesService employeesService;

    public EmployeesController(EmployeesService employeesService) {
        this.employeesService = employeesService;
    }

    @GetMapping
    public List<EmployeeDto> listEmployees() {
        return employeesService.listEmployees();
    }
}

Architektúra

Architektúra


Lombok

  • Boilerplate kódok generálására, pl. getter/setter, konstruktor, toString(),
    equals/hashcode, logger, stb.
  • Annotation processor
  • IntelliJ IDEA támogatás: plugin és Enable annotation processing
  • @Data annotáció
    • @ToString, @EqualsAndHashCode, @Getter minden attribútumon,
      @Setter nem final attribútumon és @RequiredArgsConstructor
  • @NoArgsConstructor
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

Példa a Lombok használatára

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private long id;

    private String name;

    public Employee(String name) {
        this.name = name;
    }
}

ModelMapper

  • Object mapping
  • Hasonló struktúrájú osztályú példányok konvertálására
    (pl. entitások és DTO-k között)
  • Reflection alapú, intelligens alapértékekkel
  • Fluent mapping API speciális esetek kezelésére
<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>${modelmapper.version}</version>
</dependency>

Példa a ModelMapper
használatára

@Bean
public ModelMapper modelMapper() {
  return new ModelMapper();
}
Employee employee = // load
EmployeeDto dto = modelMapper.map(employee, EmployeeDto.class);

List<Employee> employees = // load
java.lang.reflect.Type targetListType = new TypeToken<List<EmployeeDto>>() {}.getType();
List<EmployeeDto> dtos = modelMapper.map(employees, targetListType);

class: inverse, center, middle

GET műveletek paraméterezése


URL paraméterek kezelése

  • @RequestParam annotációval
  • Kötelező, kivéve a required = "false" attribútum megadásakor
  • Automatikus típuskonverzió
public List<EmployeeDto> listEmployees(@RequestParam Optional<String> prefix) {
       return employeesService.listEmployees(prefix);
}

Elérhető a /api/employees?prefix=Jack címen


Több URL paraméter kezelése

  • Létrehozni egy objektumot
  • Nem szükséges annotáció
public List<EmployeeDto> listEmployees(QueryParameters parameters) {
   return employeesService.listEmployees(parameters);
}
@Data
public class QueryParameters {
    private String prefix;

    private String postfix;
}

URL részletek kezelése

  • Osztályon lévő @RequestMapping és @GetMapping összeadódik
@GetMapping("/{id}")
public EmployeeDto findEmployeeById(@PathVariable("id") long id) {
    return employeesService.findEmployeeById(id);
}

Elérhető a /api/employees/1 címen


12Factor hivatkozás:
Stateless processes

  • Csak egy kérés időtartamáig van állapot
    • Nem baj, ha elveszik
    • Nem kell cluster-ezni
    • Kevesebb erőforrás
    • Nincs párhuzamossági probléma
  • Kérések közötti állapot: backing services
  • Cache: inkább backing service, ne nőjön szignifikánsan
    az alkalmazás memóriaigénye
  • Shared nothing: egy update csak egy node hatóköre

Konkurrencia

  • Ha állapotmentesen dolgozunk, nem okoz problémát
  • Horizontális skálázás
  • Backing service szintre kerül

class: inverse, center, middle

REST webszolgáltatások POST és DELETE művelet


POST és PUT művelet

  • PUT idempotens

Controller POST művelettel

  • @RequestBody annotáció - deszerializáció, alapból JSON-ből Jacksonnel
@PostMapping
public EmployeeDto createEmployee(
    @RequestBody CreateEmployeeCommand command) {
  return employeesService.createEmployee(command);
}

@PutMapping("/{id}")
public EmployeeDto updateEmployee(
    @PathVariable("id") long id,
    @RequestBody UpdateEmployeeCommand command) {
  return
    employeesService.updateEmployee(id, command);
}

Controller DELETE művelettel

@DeleteMapping("/{id}")
public void deleteEmployee(@PathVariable("id") long id) {
    employeesService.deleteEmployee(id);
}

class: inverse, center, middle

Státuszkódok és hibakezelés


Státuszkód állítása controller metódusból

  • ResponseEntity visszatérési típus: státuszkód, header, body, stb.
@GetMapping("/{id}")
public ResponseEntity findEmployeeById(@PathVariable("id") long id) {
    try {
        return ResponseEntity.ok(employeesService.findEmployeeById(id));
    }
    catch (IllegalArgumentException iae) {
        return ResponseEntity.notFound().build();
    }
}

201 - CREATED státuszkód

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public EmployeeDto createEmployee(
    @RequestBody CreateEmployeeCommand command) {
  return employeesService.createEmployee(command);
}

201 - Location header

@PostMapping
public ResponseEntity<EmployeeDto> createEmployee(@RequestBody CreateEmployeeCommand command, 
        UriComponentsBuilder uri) {
    EmployeeDto employeeDto = employeeService.createEmployee(command);
    return ResponseEntity
        .created(uri.path("/api/employees/{id}").buildAndExpand(employeeDto.getId()).toUri())
        .body(employeeDto);
}

Unit teszt:

UriComponentsBuilder builder = mock(UriComponentsBuilder.class);
UriComponents components = mock(UriComponents.class);
when(builder.path(any())).thenReturn(builder);
when(builder.buildAndExpand(anyLong())).thenReturn(components);

EmployeeDto employeeDto = employeesController
    .createEmployee(new CreateEmployeeCommand("John Doe"), builder)
    .getBody();

204 - NO CONTENT státuszkód

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteEmployee(@PathVariable("id") long id) {
    employeesService.deleteEmployee(id);
}

Alapértelmezett válasz hiba

  • Status code 500
{
  "timestamp": 1596570258672,
  "status": 500,
  "error": "Internal Server Error",
  "message": "",
  "path": "/api/employees/3"
}

Hibakezelés

  • Servlet szabvány szerint web.xml állományban
  • Exceptionre tehető @ResponseStatus annotáció
  • Globálisan ExceptionResolver osztályokkal
  • @ExceptionHandler annotációval ellátott metódus a controllerben
  • @ControllerAdvice annotációval ellátott globális @ExceptionHandler
    annotációval ellátott metódus

ExceptionHandler

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public void handleNotFound() {
    System.out.println("Employee not found");
}

RFC 7807

  • Problem Details for HTTP APIs
  • application/problem+json mime-type
{
    "type": "employees/invalid-json-request",
    "title": "JSON error",
    "status": 400,
    "detail": "JSON parse error: Unexpected character..."
}

RFC 7807 mezők

  • type: URI, mely azonosítja a hiba típusát
  • title: ember által olvasható üzenet
  • status: http státuszkód
  • detail: részletek, ember által olvasható
  • instance: URI, mely azonosítja a hibát,
    és később is elérhető (pl. valamilyen log hivatkozás)
  • Egyedi saját mezők definiálhatók

org.zalando:problem

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>problem</artifactId>
  <version>${problem.version}</version>
</dependency>
<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>jackson-datatype-problem</artifactId>
  <version>${problem.version}</version>
</dependency>

A problem használata

@ExceptionHandler({IllegalArgumentException.class})
public ResponseEntity<Problem> handleNotFound(IllegalArgumentException  e) {
    Problem problem = Problem.builder()
            .withType(URI.create("employees/employee-not-found"))
            .withTitle("Not found")
            .withStatus(Status.NOT_FOUND)
            .withDetail(e.getMessage())
            .build();

    return ResponseEntity
      .status(HttpStatus.NOT_FOUND)
    .contentType(MediaType.APPLICATION_PROBLEM_JSON)
      .body(problem);
}
@Bean
public ObjectMapper objectMapper() {
  return new ObjectMapper()
  .findAndRegisterModules();
}

problem-spring-web-starter

  • Integrált Spring MVC kivételkezelés és Problem 3rd-party library
<dependency>
   <groupId>org.zalando</groupId>
   <artifactId>problem-spring-web-starter</artifactId>
   <version>0.26.2</version>
</dependency>

Hiba személyre szabása

  • Beépítetten több kivételt kezel
  • Vagy a AbstractThrowableProblem kivételtől származik a saját kivétel osztályunk
  • Saját AdviceTrait implementálása, mely saját Problem példányt hoz létre saját kivétel osztályunk esetén
public class EmployeeNotFoundException extends AbstractThrowableProblem {

    private static final URI TYPE
            = URI.create("employees/employee-not-found");

    public EmployeeNotFoundException(Long id) {
        super(
                TYPE,
                "Not found",
                Status.NOT_FOUND,
                String.format("Employee with id '%d' not found", id));
    }
}

Problem könyvtártól független
kivétel

@ControllerAdvice
public class EmployeesExceptionHandler implements ProblemHandling {

    @ExceptionHandler
    ResponseEntity<Problem> handleException(EmployeeNotFoundException exception,
            NativeWebRequest request) {
        Problem problem =
            Problem.builder()
                .withType(URI.create("employees/employee-not-found"))
                .withTitle("Not found")
                .withStatus(Status.NOT_FOUND)
                .withDetail(exception.getMessage())
                .build();
        return this.create(exception, problem, request);
    }
}

class: inverse, center, middle

Integrációs tesztelés


Web réteg tesztelése

  • Elindítható csak a Spring MVC réteg:
    @SpringBootTest helyett @WebMvcTest annotáció használata
  • Service réteg mockolható Mockitoval, @MockBean annotációval
  • MockMvc injektálható
    • Kérések összeállítására (path variable, paraméterek, header, stb.)
    • Válasz ellenőrzésére (státuszkód, header, tartalom)
    • Válasz naplózására
    • Válasz akár Stringként, JSON dokumentumként
      (jsonPath)
  • Nem indít valódi konténert, a Servlet API-t mockolja
  • JSON szerializáció

Web réteg tesztelése példa

.small-code-14[

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@Test
void testListEmployees() throws Exception {
    when(employeesService.listEmployees(any())).thenReturn(List.of(
            new EmployeeDto(1L, "John Doe"),
            new EmployeeDto(2L, "Jane Doe")
    ));

    mockMvc.perform(get("/api/employees"))
      .andDo(print())
            .andExpect(status().isOk())        
            .andExpect(jsonPath("$[0].name", equalTo("John Doe")));
}

]


Teljes alkalmazás tesztelése
konténer nélkül

  • @SpringBootTest és @AutoConfigureMockMvc annotáció
@Test
void testListEmployees() throws Exception {
  mockMvc.perform(get("/api/employees"))
    .andExpect(status().isOk())
    .andDo(print())
    .andExpect(
      jsonPath("$[0].name", equalTo("John Doe")));
}

Teljes alkalmazás tesztelése
konténerrel

  • RANDOM_PORT
  • Port @LocalServerPort annotációval injektálható
  • Injektálható TestRestTemplate - url és port előre beállítva
  • JSON szerializáció és deszerializáció
@SpringBootTest(webEnvironment =
    SpringBootTest.WebEnvironment.RANDOM_PORT)

Teljes alkalmazás tesztelése
konténerrel példa

@Test
void testListEmployees() {
  List<EmployeeDto> employees = 
    restTemplate.exchange("/api/employees",
      HttpMethod.GET,
      null,
      new ParameterizedTypeReference<List<EmployeeDto>>(){})
    .getBody();
  
  assertThat(employees)
          .extracting(EmployeeDto::getName)
          .containsExactly("John Doe", "Jane Doe");
}

class: inverse, center, middle

Tesztelés WebClient használatával


WebClient

  • Spring Framework 5.0 vezette be alapvetően WebFlux integrációs tesztekhez, de működik Spring MVC-vel is
  • Fluent API assertionök írásához
  • Szükséges a org.springframework.boot:spring-boot-starter-webflux függőség
  • Spring MVC esetén nem használható, csak valós konténerrel

Egyszerű kérések és assert

  • Metódus, uri, kérés törzse és státuszkód
webClient.post().uri("/api/employees")
        .bodyValue(new CreateEmployeeCommand(("John Doe")))
        .exchange()
        .expectStatus().isCreated()

A post() mellett get(), put() és delete() metódusok


URI

Path variable (URI variable)

webClient.get().uri("/api/employees/{id}", 1)

Request parameter (query parameter)

webClient.get().uri(builder -> builder.path("/api/employees").queryParam("prefix", "j").build())

Paraméterként Function


Válasz értelmezése JSON-ként

.expectBody(String.class).value(s -> System.out.println(s))

Paraméterként Consumer

JSON Path

.expectBody().jsonPath("name").isEqualTo("John Doe");

Egyszerű objektum és lista

.expectBody(EmployeeDto.class).value(e -> assertEquals("John Doe", e.getName()));

Lista

.expectBodyList(EmployeeDto.class).hasSize(2).contains(new EmployeeDto(1L, "John Doe"));

class: inverse, center, middle

Swagger UI


Swagger UI

  • API dokumentáció generálására
  • Az API ki is próbálható
  • OpenAPI Specification (eredetileg Swagger Specification)
    • RESTful webszolgáltatások leírására
    • Kód és dokumentáció generálás
    • Programozási nyelv független
    • JSON/YAML formátumú
    • JSON Scheman alapul
  • Keretrendszer független
  • Annotációkkal személyre szabható

springdoc-openapi projekt

  • Swagger UI automatikus elindítása a /swagger-ui.html címen
  • OpenAPI elérhetőség a /v3/api-docs címen (vagy /v3/api-docs.yaml)
<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-ui</artifactId>
  <version>${springdoc-openapi-ui.version}</version>
</dependency>

Globális testreszabás

@Bean
public OpenAPI customOpenAPI() {
  return new OpenAPI()
  .info(new Info()
  .title("Employees API")
  .version("1.0.0")
  .description("Operations with employees"));
}

Séma testreszabás

  • Figyelembe veszi a Bean Validation annotációkat
public class CreateEmployeeCommand {

    @Schema(description="name of the employee", example = "John Doe")
    private String name;
}

Osztály és metódus szint

  • Figyelembe veszi a Spring MVC annotációkat
@RestController
@RequestMapping("/api/employees")
@Tag( name = "Operations on employees")
public class EmployeesController {

  @GetMapping("/{id}")
  @Operation(summary = "Find employee by id",
    description = "Find employee by id.")
  @ApiResponse(responseCode = "404",
    description = "Employee not found")
  public EmployeeDto findEmployeeById(
      @Parameter(description = "Id of the employee",
        example = "12")
      @PathVariable("id") long id) {
    // ...
  }

}

12Factor hivatkozás: API first

  • Contract first alapjain
  • Laza csatolás
  • Webes és mobil GUI és az üzleti logika is ide tartozik
  • Dokumentálva és tesztelve legyen
  • API Blueprint: Markdown alapú formátum API dokumentálására

class: inverse, center, middle

Tesztelés REST Assured használatával


REST Assured

  • Keretrendszer független eszköz REST API tesztelésére
  • Dinamikus nyelvek egyszerűségét próbálja hozni Java nyelven
  • JSON, mint szöveg, vagy objektum mapping (Jackson, Gson, JAXB, stb.)
  • Megadható
    • Path, parameter, header, cookie, content-type, stb.
  • Sokszínű assertek
  • Támogatja a különböző autentikációs módokat

REST Assured - Assert

  • XML tartalomra XmlPath, GPath (Groovy-ból, hasonló az XPath-hoz)
  • DTD és XSD validáció
  • JSON tartalomra JSONPath-szal
  • JSON Schema validáció
  • Header, status, cookie, content-type
  • Response time

Függőségek - JsonPath és XmlPath

<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>json-path</artifactId>
  <version>${rest-assured.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>xml-path</artifactId>
  <version>${rest-assured.version}</version>
  <scope>test</scope>
</dependency>

Csak JSON használata esetén is kell mindkét függőség


Függőségek

  • Un. RestAssuredMockMvc API
<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>rest-assured</artifactId>
  <version>${rest-assured.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>spring-mock-mvc</artifactId>
  <version>${rest-assured.version}</version>
  <scope>test</scope>
</dependency>

Inicializálás

import io.restassured.http.ContentType;
import io.restassured.module.mockmvc.RestAssuredMockMvc;

import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
import static org.hamcrest.Matchers.equalTo;
@Autowired
WebApplicationContext webApplicationContext;

@BeforeEach
void init() {
  RestAssuredMockMvc.requestSpecification = given()
    .contentType(ContentType.JSON)
    .accept(ContentType.JSON);

  RestAssuredMockMvc
    .webAppContextSetup(webApplicationContext);
}

Teszteset

@Test
void testCreateEmployeeThenListEmployees() {
    with().body(new CreateEmployeeCommand("Jack Doe")).
    when()
      .post("/api/employees")
      .then()
      .body("name", equalTo("Jack Doe"));

    when()
      .get("/api/employees")
      .then()
      .body("[0].name", equalTo("Jack Doe"));
}

class: inverse, center, middle

REST Assured séma validáció


JSON Schema

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "title": "Employees",
  "type": "array",
  "items": [
    {
      "title": "EmployeeDto",
      "type": "object",
      "required": ["name", "id"],
      "properties": {
        "id": {
          "type": "integer",
          "description": "id of the employee",
          "format": "int64",
          "example": 12
        },
        "name": {
          "type": "string",
          "description": "name of the employee",
          "example": "John Doe"
}}}]}

Függőség

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-schema-validator</artifactId>
    <scope>test</scope>
</dependency>

JSON Schema validáció

when()
    .get("/api/employees")
    .then()
    .body(matchesJsonSchemaInClasspath("employee-dto-schema.json"));

class: inverse, center, middle

Content negotiation


Content negotiation

  • Mechanizmus, mely lehetővé teszi a kliens számára,
    hogy az erőforrás megjelenítési formái közül válasszon, pl.
    • JSON vagy XML (Accept fejléc és Mime Type)
    • GIF vagy JPEG
    • Nyelv (Accept-Language fejléc alapján)

Content negotiation Spring Boot támogatás

  • Controllerben
@RequestMapping(value = "/api/employees",
  produces = {MediaType.APPLICATION_JSON_VALUE,
    MediaType.APPLICATION_XML_VALUE})
  • pom.xml-ben
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
</dependency>
  • Dto-ban @XmlRootElement

Lista esetén

@Data
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "employees")
@XmlAccessorType(XmlAccessType.FIELD)
public class EmployeesDto {

    @XmlElement(name = "employee")
    private List<EmployeeDto> employees;
}

class: inverse, center, middle

Validáció


Validáció

  • Bean Validation 2.0 (JSR 380) támogatás
  • Ne réteghez legyen kötve, hanem az adatot hordozó beanhez
  • Attribútumokra annotáció
  • Beépített annotációk
  • Saját annotáció implementálható
  • Megadható metódus paraméterekre és visszatérési értékre is
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Beépített annotációk 1.

  • @AssertFalse, @AssertTrue
  • @Null, @NotNull
  • @Size
  • @Max, @Min, @Positive,
    @PositiveOrZero, @Negative, @NegativeOrZero
  • @DecimalMax, @DecimalMin
  • @Digits

Beépített annotációk 2.

  • @Future, @Past,
    @PastOrPresent, @FutureOrPresent
  • @Pattern
  • @Email
  • @NotEmpty, @NotBlank

Validáció controlleren

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateEmployeeCommand {

    @NotNull(message = "Name can not be null")
    private String name;
}
@PostMapping
public EmployeeDto createEmployee(
    @Valid @RequestBody CreateEmployeeCommand command) {
  return employeesService.createEmployee(command);
}

Válasz

{
  "timestamp": 1596570707472,
  "status": 400,
  "error": "Bad Request",
  "message": "",
  "path": "/api/employees"
}

problem használatával

.small-code-14[

@ExceptionHandler({MethodArgumentNotValidException.class})
public ResponseEntity<Problem> handleValidationError(MethodArgumentNotValidException e) {
    List<Violation> violations = e.getBindingResult().getFieldErrors().stream()
            .map((FieldError fe) -> new Violation(fe.getField(), fe.getDefaultMessage()))
            .collect(Collectors.toList());

    Problem problem = Problem.builder()
            .withType(URI.create("employees/validation-error"))
            .withTitle("Validation error")
            .withStatus(Status.BAD_REQUEST)
            .withDetail(e.getMessage())
            .with("violations", violations)
            .build();

    return ResponseEntity
      .status(HttpStatus.BAD_REQUEST)
      .contentType(MediaType.APPLICATION_PROBLEM_JSON)
      .body(problem);
}

]


Violation osztály

@Data
@AllArgsConstructor
public class Violation {

    private String field;

    private String defaultMessage;
}

Kapott válasz

{
  "type": "employees/validation-error",
  "title": "Validation error",
  "status": 400,
  "detail": "Validation failed for argument [0] in public ...",
  "violations": [
    {
      "field": "name",
      "message": "Name can not be null"
    }
  ]
}

problem-spring-web-starter

{
  "type": "https://zalando.github.io/problem/constraint-violation",
  "status": 400,
  "violations": [
    {
      "field": "name",
      "message": "Name can not be null"
    }
  ],
  "title": "Constraint Violation"
}

class: inverse, center, middle

Saját validáció készítése


Saját annotáció

@Constraint(validatedBy = NameValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface Name {

    String message() default "Invalid name";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    int maxLength() default 50;

}

Validator osztály

public class NameValidator implements ConstraintValidator<Name, String> {

  private int maxLength;

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return value != null &&
      !value.isBlank() &&
      value.length() > 2 && 
      value.length() <= maxLength && 
      Character.isUpperCase(value.charAt(0));
  }

  @Override
  public void initialize(Name constraintAnnotation) {
      maxLength = constraintAnnotation.maxLength();
  }
}

class: inverse, center, middle

Spring Boot konfiguráció


Externalized Configuration

  • Konfiguráció alkalmazáson kívül szervezése,
    hogy ugyanazon alkalmazás több környezetben is tudjon futni
  • Spring Environment absztrakcióra épül, PropertySource implementációk
    hierarchiája, melyek különböző helyekről töltenek be property-ket
  • Majdnem húsz forrása a property-knek
    (a magasabb prioritásúak felülírják a később szereplőket)
  • Leggyakoribb az application.properties fájl
  • YAML formátum is használható

Források

  • Az elöl szereplők felülírják a később szereplőket
  • Legfontosabbak:
    • Parancssori paraméterek
    • Operációs rendszer környezeti változók
    • application.properties állomány a jar fájlon kívül
      (/config könyvtár, vagy közvetlenül a jar mellett)
    • application.properties állomány a jar fájlon belül

Konfiguráció beolvasása @Value annotációval

@Service
public class HelloService {

  private String hello;

  public HelloService(@Value("${employees.hello}") String hello) {
    this.hello = hello;
  }

  public String sayHello() {
    return hello + " " + LocalDateTime.now();
  }
}

application.properties tartalma

employees.hello = Hello Spring Boot Config

ConfigurationProperties

  • Több, esetleg hierarchikus property-k esetén
@ConfigurationProperties(prefix = "employees")
@Data
public class HelloProperties {

    private String hello;
}
  • Regisztrálni kell a @EnableConfigurationProperties(HelloProperties.class) annotációval, pl. a service-en
  • Használat helyén injektálható

További ConfigurationProperties lehetőségek

  • Setteren keresztül, de használható a @ConstructorBinding, ekkor konstruktoron keresztül
  • Relaxed binding: nem kell pontos egyezőség
  • Használható a @Validated Spring annotáció,
    (majd használható a Bean Validation)
  • A property-ket definiálni lehet külön állományban,
    ekkor felismeri az IDE
META-INF/additional-spring-configuration-metadata.json

Előre definiált property-k


Property-k titkosítása


Port

  • server.port

12Factor: Port binding

  • Konfigurálható legyen a port, ahol elindul
  • Két alkalmazás ne legyen telepítve ugyanarra a web konténerre,
    alkalmazásszerverre

Konfiguráció Dockerrel

docker run -d -p 8080:8081 -e SERVER_PORT=8081 -e EMPLOYEES_HELLO=HelloDocker employees

12Factor: Configuration

  • Környezetenként eltérő értékek
  • Pl. backing service-ek elérhetőségei
  • Ide tartoznak a jelszavak, titkos kulcsok,
    melyeket különös figyelemmel kell kezelni
  • Konfigurációs paraméterek a környezet részét képezzék,
    és ne az alkalmazás részét
  • Konfigurációs paraméterek környezeti változókból jöjjenek
  • Kerüljük az alkalmazásban a környezetek nevesítését
  • Nem kerülhetnek a kód mellé a verziókezelőbe
    (csak a fejlesztőkörnyezet default beállításai)
  • Verziókezelve legyen, ki, mikor mit módosított
  • Lásd még Spring Cloud Config

class: inverse, center, middle

Spring Boot naplózás


Naplózás

  • Spring belül a Commons Loggingot használja
  • Előre be van konfigurálva a Java Util Logging, Log4J2, és Logback
  • Alapesetben konzolra ír
  • Naplózás szintje, és fájlba írás is állítható
    az application.properties állományban

Best practice

  • SLF4J használata
  • Lombok használata
  • Paraméterezett üzenet
private static final org.slf4j.Logger log =
  org.slf4j.LoggerFactory.getLogger(LogExample.class);
@Slf4j
log.info("Employee has been created");
log.debug("Employee has been created with name {}",
  employee.getName());

Konfiguráció

  • application.properties: szint, fájl
  • Használható logger library specifikus konfigurációs fájl (pl. logback.xml)
logging.level.training = debug

12Factor hivatkozás: Naplózás

  • Time ordered event stream
  • Nem az alkalmazás feladata a napló irányítása a megfelelő helyre,
    vagy a napló tárolása, kezelése, archiválása, görgetése, stb.
  • Írjon konzolra
  • Központi szolgáltatás: pl. ELK, Splunk, hiszen
    az alkalmazás node-ok bármikor eltűnhetnek

class: inverse, center, middle

Feature toggles


Feature toggles

  • Futásidőben funkciók be- és kikapcsolására
  • Continuous integration miatt, feature branchek csökkentésére
    • Merge conflict minimalizálására
  • Blue/green deployment támogatására: bekapcsolni csak ha minden node új verziót futtat
  • Canary release: csak a felhasználók egy részének bekapcsolni
  • Dark Launch: beérkező kérések csak egy százalékának bekapcsolni, figyelni a rendszer viselkedését
  • A/B tesztelés: két különböző megvalósítás tesztelésére
  • Circuit Breaker: problémát/terhelést okozó funkció kikapcsolása

FF4J

  • FF4J egy Javas Feature toggles implementáció
  • Támogatja a szerepkör szerinti szétválasztást, akár Spring Security keretrendszerrel
  • AOP támogatás
  • Monitorozás
  • Auditálható
  • Parancssori, JMX, REST, webes interfész
  • Választható adatbázis és cache implementációk
  • Saját stratégiákkal bővíthető
  • Spring Boot integráció

Függőségek

<dependency>
  <groupId>org.ff4j</groupId>
  <artifactId>ff4j-spring-boot-starter</artifactId>
  <version>${ff4j.version}</version>
</dependency>
<dependency>
  <groupId>org.ff4j</groupId>
  <artifactId>ff4j-web</artifactId>
  <version>${ff4j.version}</version>
  <exclusions>
    <exclusion>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
    </exclusion>
  </exclusions>
</dependency>

Feature használata

@Service
public class EmployeesService {

  private FF4j ff4j;
  
  @PostConstruct
    public void init() {
        ff4j.createFeature(new Feature(FEATURE_CHECK_UNIQUE));
    }
  
  public EmployeeDto createEmployee(CreateEmployeeCommand command) {
        if (ff4j.check(FEATURE_CHECK_UNIQUE)) {
            // Kapcsolható funkció
        }
  
  // ...
  }		
}

Feature kapcsolása

REST API

### Get feature

GET http://localhost:8080/api/ff4j/store/features/checkUnique

### Enable feature

POST http://localhost:8080/api/ff4j/store/features/checkUnique/enable
Content-Type: application/json

### Disable feature

POST http://localhost:8080/api/ff4j/store/features/checkUnique/disable
Content-Type: application/json

class: inverse, center, middle

Spring JdbcTemplate


Spring JdbcTemplate

  • JDBC túl bőbeszédű
  • Elavult kivételkezelés
    • Egy osztály, üzenet alapján megkülönböztethető
    • Checked
  • Boilerplate kódok eliminálására template-ek
  • Adatbáziskezelés SQL-lel

Architektúra

Architektúra


JDBC használata

  • org.springframework.boot:spring-boot-starter-jdbc függőség
  • Embedded adatbázis támogatás, automatikus DataSource konfiguráció
    • Pl H2: com.h2database:h2
    • Developer Tools esetén elérhető webes konzol a /h2-console címen
  • Injektálható JdbcTemplate
  • Service delegál a Repository felé

Insert, update és delete

jdbcTemplate.update(
  "insert into employees(emp_name) values ('John Doe')");

jdbcTemplate.update(
  "insert into employees(emp_name) values (?)", "John Doe");

jdbcTemplate.update(
  "update employees set emp_name = ? where id = ?", "John Doe", 1);

jdbcTemplate.update(
  "delete from employees where id = ?", 1);

Generált id lekérése

KeyHolder keyHolder = new GeneratedKeyHolder();

jdbcTemplate.update(
  con -> {
      PreparedStatement ps =
        con.prepareStatement("insert into employees(emp_name) values (?)",
          Statement.RETURN_GENERATED_KEYS);
      ps.setString(1, employee.getName());
      return ps;
  }, keyHolder);

employee.setId(keyHolder.getKey().longValue());

Select

List<Employee> employees = jdbcTemplate.query(
  "select id, emp_name from employees",
  this::convertEmployee);

Employee employee =
  jdbcTemplate.queryForObject(
    "select id, emp_name from employees where id = ?",
    this::convertEmployee,
    id);
private Employee convertEmployee(ResultSet resultSet, int i)
    throws SQLException {
  long id = resultSet.getLong("id");
  String name = resultSet.getString("emp_name");
  Employee employee = new Employee(id, name);
  return employee;
}

Séma inicializálás

.small-code-14[

@Component
@AllArgsConstructor
public class DbInitializer implements CommandLineRunner {

  private JdbcTemplate jdbcTemplate;

  @Override
  public void run(String... args) throws Exception {
    jdbcTemplate.execute("create table employees " +
      "(id bigint auto_increment, emp_name varchar(255), " +
      "primary key (id))");

    jdbcTemplate.execute(
      "insert into employees(emp_name) values ('John Doe')");
    jdbcTemplate.execute(
      "insert into employees(emp_name) values ('Jack Doe')");
  }
}

]


class: inverse, center, middle

Spring Data JPA


Spring Data JPA

  • Egyszerűbbé teszi a perzisztens réteg implementálását
  • Tipikusan CRUD műveletek támogatására, olyan gyakori igények
    megvalósításával, mint a rendezés és a lapozás
  • Interfész alapján repository implementáció generálás
  • Query by example
  • Ismétlődő fejlesztési feladatok redukálása, boilerplate kódok csökkentése

Spring Data JPA használatba
vétele

  • org.springframework.boot:spring-boot-starter-data-jpa függőség
  • Entitás létrehozása
  • JpaRepository kiterjesztése
  • @Transactional alkalmazása a service rétegben
  • application.properties
spring.jpa.show-sql=true

JpaRepository

  • save(S), saveAll(Iterable<S>), saveAndFlush(S)
  • findById(Long), findOne(Example<S>),
    findAll() különböző paraméterezésű metódusai (lapozás, Example),
    findAllById(Iterable<ID>)
  • getOne(ID) (nem Optional példánnyal tér vissza)
  • exists(Example<S>), existsById(ID)
  • count(), count(Example<S>)
  • deleteById(ID), delete(S),
    deleteAll() üres és Iterable paraméterezéssel,
    deleteAllInBatch(),
    deleteInBatch(Iterable<S>)
  • flush()

Entitás

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "employees")
public class Employee {

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

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

    public Employee(String name) {
        this.name = name;
    }
}

Repository

public interface EmployeesRepository extends JpaRepository<Employee, Long> {

    @Query("select e from Employee e where upper(e.name) like upper(:name)")
    List<Employee> findAllByPrefix(String name);

}

class: inverse, center, middle

MariaDB


MariaDB indítása Dockerrel

docker run 
  -d
  -e MYSQL_DATABASE=employees
  -e MYSQL_USER=employees
  -e MYSQL_PASSWORD=employees
  -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
  -p 3306:3306
  --name employees-mariadb
  mariadb

Driver

pom.xml-be:

<dependency>
  <groupId>org.mariadb.jdbc</groupId>
  <artifactId>mariadb-java-client</artifactId>
  <scope>runtime</scope>
</dependency>

Inicializálás

  • application.properties konfiguráció
spring.datasource.url=jdbc:mariadb://localhost/employees
spring.datasource.username=employees
spring.datasource.password=employees

spring.jpa.hibernate.ddl-auto=create-drop

12Factor: Backing services

  • Adatbázis (akár relációs, akár NoSQL), üzenetküldő middleware-ek,
    directory és email szerverek, elosztott cache, Big Data eszközök, stb.
  • Microservice környezetben egy másik alkalmazás is
  • Automatizált telepítés
  • Infrastructure as Code, Ansible, Chef, Puppet
  • Eléréseik, autentikációs paraméterek környezeti paraméterként
    publikálódnak az alkalmazás felé
  • Fájlrendszer nem tekinthető megfelelő
    háttérszolgáltatásnak
  • Beágyazható háttérszolgáltatások
  • Redeploy nélkül megoldható legyen a kapcsolódás
  • Circuit breaker: ha nem működik a szolgáltatás,
    megszűnteti egy időre a hozzáférést (biztosíték)

class: inverse, center, middle

PostgreSQL


PostgreSQL indítása Dockerrel

docker run
  -d
  -e POSTGRES_PASSWORD=password 
  -p 5432:5432 
  --name employees-postgres 
  postgres

Driver

pom.xml-be:

<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <scope>runtime</scope>
</dependency>

Inicializálás

  • application.properties konfiguráció
spring.datasource.url=jdbc:postgresql:postgres
spring.datasource.username=postgres
spring.datasource.password=password

spring.jpa.hibernate.ddl-auto=create-drop

class: inverse, center, middle

Integrációs tesztelés


JPA repository tesztelése

  • JPA repository-k tesztelésére
  • @DataJpaTest annotáció, csak a repository réteget indítja el
    • Embedded adatbázis
    • Tesztbe injektálható: JPA repository, DataSource, JdbcTemplate,
      EntityManager
  • Minden teszt metódus saját tranzakcióban, végén rollback
  • Service réteg már nem kerül elindításra
  • Tesztelni:
    • Entitáson lévő annotációkat
    • Névkonvenció alapján generált metódusokat
    • Custom query

DataJpaTest

@DataJpaTest
public class EmployeesRepositoryIT {

  @Autowired
  EmployeesRepository employeesRepository;

  @Test
  void testPersist() {
    Employee employee = new Employee("John Doe");
    employeesRepository.save(employee);
    List<Employee> employees =
      employeesRepository.findAllByPrefix("%");
    assertThat(employees)
      .extracting(Employee::getName)
      .containsExactly("John Doe");
  }

}

@SpringBootTest használata

  • Teljes alkalmazás tesztelése
  • Valós adatbázis szükséges hozzá, gondoskodni kell az elindításáról
  • Séma inicializáció és adatfeltöltés szükséges

Tesztek H2 adatbázisban

  • src\test\resources\application.properties fájlban
    a teszteléshez használt DataSource
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

Séma inicializáció

  • spring.jpa.hibernate.ddl-auto create-drop alapesetben,
    teszt lefutása végén eldobja a sémát
    • create-re állítva megmaradnak a táblák és adatok
  • Ha van schema.sql a classpath-on, azt futtatja le
  • Flyway vagy Liquibase használata

Adatfeltöltés

  • data.sql a classpath-on
  • @Sql annotáció használata a teszten
  • Programozott módon
    • Teszt osztályban @BeforeEach vagy @AfterEach
      annotációkkal megjelölt metódusokban
    • Publikus API-n keresztül
    • Injektált controller, service, repository, stb. használatával
    • Közvetlen hozzáférés az adatbázishoz
      (pl. JdbcTemplate)

Tesztek egymásra hatása

  • Csak külön adatokon dolgozunk - nehéz lehet a kivitelezése
  • Teszteset maga előtt vagy után rendet tesz
  • Állapot
    • Teljes séma törlése, séma inicializáció
    • Adatbázis import
    • Csak (bizonyos) táblák ürítése

class: inverse, center, middle

Alkalmazás futtatása Dockerben MariaDB-vel


Architektúra

Alkalmazás futtatása Dockerben


Hálózat létrehozása

docker network ls
docker network create --driver bridge employees-net
docker network inspect employees-net

Alkalmazás futtatása Dockerben

docker run
    -d  
*    -e SPRING_DATASOURCE_URL=jdbc:mariadb://employees-mariadb/employees
*    -e SPRING_DATASOURCE_USERNAME=employees
*    -e SPRING_DATASOURCE_PASSWORD=employees
    -p 8080:8080
*    --network employees-net
    --name employees
    employees

class: inverse, center, middle

Alkalmazás futtatása Dockerben MariaDB-vel Fabric8 Docker Maven Pluginnel


Adatbázis

<image>
  <name>mariadb</name>
  <alias>employees-mariadb</alias>
  <run>
  <env>
  <MYSQL_DATABASE>employees</MYSQL_DATABASE>
  <MYSQL_USER>employees</MYSQL_USER>
  <MYSQL_PASSWORD>employees</MYSQL_PASSWORD>
  <MYSQL_ALLOW_EMPTY_PASSWORD>yes</MYSQL_ALLOW_EMPTY_PASSWORD>
  </env>
  <ports>3306:3306</ports>
  </run>
</image>

Wait

FROM adoptopenjdk:14-jre-hotspot
RUN  apt update \
     && apt-get install wget \
     && apt-get install -y netcat \
     && wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh \
     && chmod +x ./wait-for-it.sh
RUN mkdir /opt/app
ADD maven/${project.artifactId}-${project.version}.jar /opt/app/employees.jar
CMD ["./wait-for-it.sh", "-t", "180", "employees-mariadb:3306", "--", "java", "-jar", "/opt/app/employees.jar"]

Alkalmazás

<image>

  <!--- ... -->

  <run>
  <env>
  <SPRING_DATASOURCE_URL>jdbc:mariadb://employees-mariadb/employees</SPRING_DATASOURCE_URL>
  </env>
  <ports>8080:8080</ports>
  <links>
  <link>employees-mariadb:employees-mariadb</link>
  </links>
  <dependsOn>
  <container>employees-mariadb</container>
  </dependsOn>
  </run>
</image>

class: inverse, center, middle

Teljes alkalmazás futtatása docker compose-zal


docker-compose.yml

version: '3'

services:
  employees-mariadb:
    image: mariadb
    restart: always
    ports:
      - '3306:3306'
    environment:
      MYSQL_DATABASE: employees
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' # aposztrófok nélkül boolean true-ként értelmezi
      MYSQL_USER: employees
      MYSQL_PASSWORD: employees

docker-compose.yml folytatás

  employees-app:
    image: employees
    ports:
      - "8080:8080"
    restart: always
    depends_on:
      - employees-mariadb
    environment:
      SPRING_DATASOURCE_URL: 'jdbc:mariadb://employees-mariadb:3306/employees'
    command: ["./wait-for-it.sh", "-t", "120", "employees-mariadb:3306", "--", "java", "-jar", "/opt/app/employees.jar"]

class: inverse, center, middle

Integrációs tesztelés adatbázis előkészítéssel


pom.xml

<profile>
  <id>startdb</id>
  <properties>
  <docker.filter>mariadb</docker.filter>
  </properties>
  <build>
  <plugins>
  <plugin>
  <groupId>io.fabric8</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <executions>
  <execution>
  <id>start</id>
  <phase>pre-integration-test</phase>
  <goals>
  <goal>start</goal>
  </goals>
  </execution>
  <execution>
  <id>stop</id>
  <phase>post-integration-test</phase>
  <goals>
  <goal>stop</goal>
  </goals>
  </execution>
  </executions>
  </plugin>
  </plugins>
  </build>
</profile>

class: inverse, center, middle

Séma inicializálás Flyway-jel


Séma inicializálás

  • Adatbázis séma létrehozása (táblák, stb.)
  • Változások megadása
  • Metadata table alapján

Elvárások

  • SQL/XML leírás
  • Platform függetlenség
  • Lightweight
  • Visszaállás korábbi verzióra
  • Indítás paranccssorból, alkalmazásból
  • Cluster támogatás
  • Placeholder támogatás
  • Modularizáció
  • Több séma támogatása

Flyway függőség

<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>

Hibernate séma inicializálás kikapcsolás az application.properties állományban:

spring.jpa.hibernate.ddl-auto=none

Migration MariaDB esetén

src/resources/db/migration/V1__employees.sql állomány

create table employees (id bigint auto_increment,
  emp_name varchar(255), primary key (id));

insert into employees (emp_name) values ('John Doe');
insert into employees (emp_name) values ('Jack Doe');

flyway_schema_history tábla


Migration PostgreSQL esetén

src/resources/db/migration/V1__employees.sql állomány

create table employees (id int8 generated by default as identity, 
  emp_name varchar(255), primary key (id));

insert into employees (emp_name) values ('John Doe');
insert into employees (emp_name) values ('Jack Doe');

flyway_schema_history tábla


class: inverse, center, middle

Séma inicializálás Liquibase-zel


Liquibase

pom.xml-ben

<dependency>
  <groupId>org.liquibase</groupId>
  <artifactId>liquibase-core</artifactId>
</dependency>

application.properties állományban

spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml

Change log

A db.changelog-master.xml fájl:

<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
      http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
      http://www.liquibase.org/xml/ns/dbchangelog-ext 
      http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">

  <changeSet id="create-employee-table" author="vicziani">
    <sqlFile path="create-employee-table.sql"
      relativeToChangelogFile="true" />
  </changeSet>
</databaseChangeLog>

SQL migráció

create-employee-table.sql fájl MariaDB esetén:

create table employees (id bigint not null auto_increment, emp_name varchar(255), primary key (id));

create-employee-table.sql fájl PostgreSQL esetén:

create table employees (id int8 generated by default as identity, emp_name varchar(255), 
  primary key (id));

class: inverse, center, middle

MongoDB


MongoDB elindítása

docker run -d -p27017:27017 --name employees-mongo mongo

Alkalmazás előkészítése

application.properties fájlban:

spring.data.mongodb.database = employees

pom.xml függőség

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Entitás

@Data
@NoArgsConstructor
@AllArgsConstructor
* @Document("employees")
public class Employee {

*    @Id
    private String id;

    private String name;

    public Employee(String name) {
        this.name = name;
    }
}

Repository

public interface EmployeesRepository extends MongoRepository<Employee, String> {

    @Query("{ 'name': { $regex: ?0, $options: 'i'} }")
    List<Employee> findAll(String name);

}

További módosítások

  • id átírása String típusra: Dto, Controller metódus paraméterek, Service metódus paraméterek
  • EmployeesService
public EmployeeDto updateEmployee(String id, UpdateEmployeeCommand command) {
  Employee employee = employeesRepository.findById(id)
  .orElseThrow(() -> new IllegalArgumentException("Employee not found"));
  employee.setName(command.getName());
*	employeesRepository.save(employee);
  return modelMapper.map(employee, EmployeeDto.class);
}

Konzol

docker exec -it employees-mongo mongosh employees
db.employees.find()

db.employees.insertOne({"name": "John Doe"})

db.employees.findOne({'_id': ObjectId('60780cf974bc5648cf220a96')})

db.employees.deleteOne({'_id': ObjectId('60780cf974bc5648cf220a96')})

class: inverse, center, middle

OAuth 2.0 Keycloak szerverrel


12Factor hivatkozás:
Authentication and Authorization

  • Security-vel az elejétől foglalkozni kell
  • Endpoint védelem
  • Audit naplózás
  • RBAC - role based access controll
  • OAuth2

OAuth 2.0

  • Nyílt szabány erőforrás-hozzáférés kezelésére
  • Elválik, hogy a felhasználó mit is akar igénybe venni,
    és az, hogy hol jelentkezik be
  • Google, Facebook vagy saját szerver
  • Szereplők
    • Resource owner: aki hozzáfér az erőforráshoz, a szolgáltatáshoz,
      humán esetben a felhasználó (de lehet alkalmazás is)
    • Client: a szoftver, ami hozzá akar férni a
      felhasználó adataihoz
    • Authorization Server: ahol a felhasználó adatai
      tárolva vannak, és ahol be tud lépni
    • Resource Server: ahol a felhasználó igénybe veszi
      az erőforrásokat, a szolgáltatást

OAuth 2.0 forgatókönyvek

  • Grant Type:
    • Authorization Code: klasszikus mód, ahogy egy webes alkalmazásba
      lépünk Facebook vagy a Google segítségével
    • Implicit: mobil alkalmazások, vagy csak böngészőben futó alkalmazások
      használják
    • Resource Owner Password Credentials: ezt olyan megbízható
      alkalmazások használják, melyek maguk kérik be a jelszót
    • Client Credentials: ebben az esetben
      nem a felhasználó kerül azonosításra,
      hanem az alkalmazás önmaga

Authorization Code

  • A felhasználó elmegy az alkalmazás oldalára
  • Az átirányít a Authorization Serverre (pl. Google vagy Facebook),
    megadva a saját azonosítóját (client id), hogy hozzá szeretne férni
    a felhasználó adataihoz
  • Az Authorization Serveren a felhasználó bejelentkezik
  • Az Authorization Serveren a felhasználó jogosultságot ad az alkalmazásnak,
    hogy hozzáférjen a felhasználó adataihoz
  • Az Authorization Server visszairányítja a felhasználót
    az alkalmazás oldalára, url paraméterként átadva neki
    egy úgynevezett authorization code-ot
  • Az alkalmazás a kapott authorization code-ot,
    a saját azonosítóját (client id), az alkalmazáshoz
    rendelt "jelszót" (client secret) felhasználva lekéri
    az Authorization Servertől a felhasználóhoz tartozó
    tokent, mely tartalmazza a felhasználó adatait

Token

JW* szabványok


Keycloak

  • Keycloak indítása Dockerben
docker run -e KEYCLOAK_USER=root -e KEYCLOAK_PASSWORD=root -p 8081:8080
  --name keycloak jboss/keycloak
  • Létre kell hozni egy Realm-et (EmployeesRealm)
  • Létre kell hozni egy klienst, amihez meg kell adni annak azonosítóját,
    és hogy milyen url-en érhető el (employees-app)
  • Létre kell hozni a szerepköröket (employees_app_user)
  • Létre kell hozni egy felhasználót (a Email Verified legyen On értéken, hogy be lehessen vele jelentkezni), beállítani a jelszavát (a Temporary értéke legyen Off, hogy ne kelljen jelszót módosítani),
    valamint hozzáadni a szerepkört (johndoe)

Keycloak tesztelés

  • Konfiguráció leírása:

.small-code-14[

http://localhost:8081/auth/realms/EmployeesRealm/.well-known/openid-configuration

]

  • A következő címen lekérhető a tanúsítvány

.small-code-14[

http://localhost:8081/auth/realms/EmployeesRealm/protocol/openid-connect/certs

]


Token lekérése

.small-code-14[

curl -s --data "grant_type=password&client_id=employees-app&username=johndoe&password=johndoe"
  http://localhost:8081/auth/realms/EmployeesRealm/protocol/openid-connect/token | jq

]

.small-code-14[

POST http://localhost:8081/auth/realms/EmployeesRealm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=password&client_id=employees-app&username=johndoe&password=johndoe

]


Keycloak tesztelés Postmanből

Be kell írni egy létező scope-ot (pl. profile), mert üreset nem tud értelmezni

Keycloak tesztelés Postmanből


Spring támogatás

  • Spring Security OAuth deprecated
  • Spring Security 5.2 majdnem teljes támogatás
    • Authorization Server nélkül
  • Külön Keycloak integráció

.small-code-14[

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.keycloak.bom</groupId>
      <artifactId>keycloak-adapter-bom</artifactId>
      <version>14.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

]

.small-code-14[

<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>

]


Konfiguráció

  • application.properties:

.small-code-14[

keycloak.auth-server-url=http://localhost:8081/auth
keycloak.realm=EmployeesRealm
keycloak.resource=employees-app
keycloak.bearer-only=true

keycloak.security-constraints[0].authRoles[0]=employees_app_user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/*

keycloak.principal-attribute=preferred_username

]


Kérés

GET http://localhost:8080/api/employees
Accept: application/json
Authorization: bearer eyJ...

Felhasználónévhez hozzáférés

@GetMapping
public List<EmployeeDto> employees(Principal principal) {
    log.info("Logged in user: {}", principal.getName());
    return employeeService.listEmployees();
}

class: inverse, center, middle

RestTemplate


Architektúra

RestTemplate architektúra


Lépések

  • Addresses alkalmazás elindítása Docker Hub alapján
docker run -d -p 8081:8080 --name my-addresses training360/addresses
  • Addresses alkalmazás elindítása forrás alapján
mvnw package
docker build -t addresses .
docker run -d -p 8081:8080 --name my-addresses addresses

Forrás

@Service
@Slf4j
public class AddressesGateway {

    private final RestTemplate restTemplate;

    private String url;

    public AddressesGateway(RestTemplateBuilder builder, 
            @Value("${employees.addresses.url}") String url) {
        restTemplate = builder.build();
        this.url = url;
    }

    public AddressDto findAddressByName(String name) {
        log.debug("Get address from Addresses application");
        return restTemplate.getForObject(url, AddressDto.class, name);
    }
}

application.properties

employees.addresses.url = http://localhost:8081/api/addresses?name={name}

class: inverse, center, middle

WebClient


Integráció WebClienttel függőség

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Integráció WebClienttel

@Service
public class AddressesGateway {

    public AddressDto findAddressByName(String name) {
        return WebClient.create("http://localhost:8081")
                .get()
                .uri(builder -> builder.path("/api/addresses").queryParam("name", name).build())
                .retrieve()
                .bodyToMono(AddressDto.class)
                .block();
    }
}

Nagyvállalati megoldás

  • Konfiguráció @ConfigurationProperties használatával
  • Saját annotáció
  • Hibakezelés
  • Thread-ek száma
  • Timeout

.small-code-14[

String connectionProviderName = "myConnectionProvider";
HttpClient httpClient = HttpClient.create(ConnectionProvider.create(connectionProviderName, 5));

WebClient webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient))
        .baseUrl(gatewayProperties.getUrl())
        .build();

return webClient
        .get()
        .uri(builder -> builder.path("/api/addresses").queryParam("name", name).build())
        .retrieve()
        .bodyToMono(AddressDto.class)
        .timeout(Duration.parse("PT5S"))
        .onErrorReturn(new AddressDto())
        .block();

]


class: inverse, center, middle

RestTemplate integrációs tesztelése


RestClientTest

.small-code-14[

@RestClientTest(value = AddressesGateway.class, 
  properties = "employees.addresses.url = http://localhost:8080/api/addresses?name={name}")
public class AddressesGatewayRestTemplateIT {

    @Autowired
    AddressesGateway addressesGateway;

    @Autowired
    MockRestServiceServer server;

    @Test
    void testFindAddressByName() throws JsonProcessingException {
        server.expect(requestTo(startsWith("http://localhost:8080/api/addresses")))
                .andExpect(queryParam("name", "John%20Doe"))
                .andRespond(withSuccess("{\"city\": \"Budapest\", \"address\": \"Andrássy u. 2.\"}"
                  , MediaType.APPLICATION_JSON));

        AddressDto addressDto = addressesGateway.findAddressByName("John Doe");

        assertEquals("Budapest", addressDto.getCity());
        assertEquals("Andrássy u. 2.", addressDto.getAddress());
    }
}

]


ObjectMapper

public class AddressesGatewayRestTemplateIT {

    // ...

    @Autowired
    ObjectMapper objectMapper;

    @Test
    void testFindAddressByName() throws JsonProcessingException {
        String json = objectMapper.writeValueAsString(new AddressDto("Budapest", "Andrássy u. 2."));

        // ...
    }
}

class: inverse, center, middle

WireMock


WireMock

  • Eszköz HTTP-alapú, pl. REST API mockolásra
  • Kapcsolódó rendszer helyettesítésére
  • Megadható, hogy milyen URL-en milyen választ adjon vissza
    (request matching, stubbing)
  • Ellenőrizni lehet, hogy milyen kérések mentek felé (verification)
  • Szimulálható hibás működés (pl. státuszkódok, timeoutok)
  • Futtatható önállóan, vagy JUnit tesztbe ágyazva
  • Képes felvenni és visszajátszani kommunikációt
  • REST és Java API
  • Konfigurálható akár JSON állományokkal is
  • Spring Boot integráció: Spring Cloud Contract WireMock

Függőség

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <version>3.1.3</version>
    <scope>test</scope>
</dependency>

Teszteset

.small-code-14[

@SpringBootTest
@AutoConfigureWireMock(port = 8081)
class AddressesGatewayWireMockIT {

    @Autowired
    AddressesGateway addressesGateway;

    @Test
    void testFindAddressByName() {
      String resource = "/api/addresses";

      stubFor(get(urlPathEqualTo("/api/addresses"))
        .willReturn(aResponse()
        .withHeader("Content-Type", "application/json")
        .withBody("{\"city\": \"Budapest\", \"address\": \"Andrássy u. 2.\"}")));

      Address address = gateway.findAddressByName("John Doe");

      verify(getRequestedFor(urlPathEqualTo(resource))
        .withQueryParam("name", equalTo("John Doe")));
        
      assertThat(address.getCity()).isEqualTo("Budapest");
      assertThat(address.getAddress()).isEqualTo("Andrássy u. 2.");
    }

}

]


class: inverse, center, middle

JMS infrastruktúra


Message Oriented Middleware

  • Rendszerek közötti üzenetküldés
  • Megbízható üzenetküldés: store and forward
  • Következő esetekben alkalmazható hatékonyan
    • Hívott fél megbízhatatlan
    • Kommunikációs csatorna megbízhatatlan
    • Hívott fél lassan válaszol
    • Terheléselosztás
    • Heterogén rendszerek
  • Lazán kapcsolt rendszerek: nem kell ismerni a
    címzettet

JMS

  • Szabványos Java API MOM-ekhez való hozzáféréshez
  • Java EE része, de Java SE-ben is használható
  • JMS provider
    • IBM MQ, Apache ActiveMQ (ActiveMQ 5 "Classic", ActiveMQ Artemis),
      RabbitMQ
  • Hozzáférés JMS API-n keresztül

Architektúra

JMS architektúra


Lépések

  • Hálózat létrehozása
docker network create --driver bridge eventstore-net
  • Artemis indítása
docker build -t eventstore-mq .
docker run -d -p 8161:8161 -p 61616:61616
  --network eventstore-net --name employees-mq eventstore-mq
  • EventStore indítása
mvnw package
docker build -t eventstore .
docker run -d -p 8082:8080
  -e SPRING_ARTEMIS_HOST=eventstore-mq
  --network eventstore-net --name eventstore eventstore

Artemis konfig

.small-code-14[

FROM adoptopenjdk:14-jdk-hotspot

RUN apt-get update \
  && apt-get install wget \
  && wget -q -O /tmp/apache-artemis-2.13.0-bin.tar.gz \
  "https://www.apache.org/dyn/closer.cgi?filename=activemq/activemq-artemis/2.13.0/apache-artemis-2.13.0-bin.tar.gz&action=download" \
  && tar xzf /tmp/apache-artemis-2.13.0-bin.tar.gz -C /opt \
  && rm /tmp/apache-artemis-2.13.0-bin.tar.gz

WORKDIR /var/lib
RUN /opt/apache-artemis-2.13.0/bin/artemis create --http-host 0.0.0.0 --relax-jolokia \
  --queues eventsQueue --allow-anonymous --user artemis --password artemis eventstorebroker

RUN sed -i "s|<max-disk-usage>90</max-disk-usage>|<max-disk-usage>100</max-disk-usage>|g"
  \ /var/lib/eventstorebroker/etc/broker.xml

EXPOSE 8161
EXPOSE 61616

CMD ["/var/lib/eventstorebroker/bin/artemis", "run"]

]

  • --relax-jolokia admin felület elérhető legyen kintről
  • max-disk-usage, különben blokkolja a küldést

Lépések meglévő image-k alapján

  • Hálózat létrehozása
docker network create --driver bridge eventstore-net
  • Artemis indítása
docker run -d -p 8161:8161 -p 61616:61616 
  --network eventstore-net --name eventstore-mq training360/eventstore-mq
  • EventStore indítása
docker run -d -p 8082:8080
  -e SPRING_ARTEMIS_HOST=eventstore-mq
  --network eventstore-net
  --name my-eventstore training360/eventstore

docker-compose.yaml

version: '3'

services:
  eventstore-mq:
    image: training360/eventstore-mq
    restart: always
    ports:
      - "8161:8161"
      - "61616:61616"
  eventstore:
    image: training360/eventstore
    restart: always
    depends_on:
      - eventstore-mq
    ports:
      - "8082:8080"
    environment:
      SPRING_ARTEMIS_HOST: 'eventstore-mq'
    entrypoint: ["./wait-for-it.sh", "-t", "120", "eventstore-mq:61616", "--", 
               "java", "org.springframework.boot.loader.JarLauncher"]

Docker compose értelmezés

  • depends_on: indítási sorrend
  • restart: always: hiba esetén vagy Docker daemon újraindításkor is újraindítja (pl. számítógép reboot esetén is)

wait-for-it beszerzése

RUN  apt-get update \
     && apt-get install wget \
     && apt-get install -y netcat \
     && wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh \
     && chmod +x ./wait-for-it.sh

Indítás Docker compose-zal

docker-compose up

docker-compose up -d

docker-compose down

Artemis admin felület

  • Elérhető a http://localhost:8161 címen.
  • Alapértelmezett felhasználónév/jelszó: admin / admin

class: inverse, center, middle

JMS üzenet küldése


Spring üzenetküldés konfig

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
  • Ekkor a localhosthoz, default porton (61616) kapcsolódik
  • Felülbírálható a spring.artemis.host és spring.artemis.port
    paraméterekkel

Üzenetküldés

  • Injektálható JmsTemplate segítségével
public class EventStoreGateway {

    private JmsTemplate jmsTemplate;

    public EventStoreGateway(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendEvent(EmployeeHasCreatedEvent event) {
        log.debug("Send event to eventstore");
        jmsTemplate.convertAndSend("eventsQueue", event);
    }
}

Konvertálás

  • Alapesetben a SimpleMessageConverter aktív
    • String -> TextMessage
    • byte[] -> BytesMessage
    • Map -> MapMessage
    • Serializable -> ObjectMessage

Konvertálás JSON-be

  • MarshallingMessageConverter (JAXB), vagy MappingJackson2MessageConverter (JSON)
@Bean
public MessageConverter messageConverter(ObjectMapper objectMapper){
  MappingJackson2MessageConverter converter =
  new MappingJackson2MessageConverter();
  converter.setTypeIdPropertyName("_typeId");
  return converter;
}
  • A cél a _typeId értékéből (header-ben utazik) találja ki,
    hogy milyen osztállyá kell alakítani (unmarshal)

Típus megadása

  • Alapesetben a típus értéke fully qualified classname - lehet, hogy a cél oldalon nem mond semmit
  • Ezért hozzárendelünk egy stringet
MappingJackson2MessageConverter converter
  = new MappingJackson2MessageConverter();
converter.setTypeIdPropertyName("_typeId");
converter.setTypeIdMappings(
  Map.of("CreateEventCommand", EmployeeHasCreatedEvent.class));

class: inverse, center, middle

JMS üzenet fogadása


JMS üzenet fogadása

@JmsListener(destination = "eventsQueue")
public void processMessage(CreateEventCommand command) {
    eventsService.createEvent(command, "JMS");
}

class: inverse, center, middle

Actuator


Actuator

  • Monitorozás, beavatkozás és metrikák
  • HTTP és JMX végpontok

Actuator alapok

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • http://localhost:8080/actuator címen elérhető az
    enabled és exposed endpontok listája
  • Logban:
o.s.b.a.e.web.EndpointLinksResolver:
  Exposing 2 endpoint(s) beneath base path '/actuator'
  • További actuator végpontok bekapcsolása:
    management.endpoints.web.exposure.include
    konfigurációval

Actuator haladó

  • Összes expose: management.endpoints.web.exposure.include = *
  • Mind be van kapcsolva, kivéve a shutdown
management.endpoint.shutdown.enabled = true
  • Saját fejleszthető
  • Biztonságossá kell tenni

Health

{"status":"UP"}
management.endpoint.health.show-details = always
  • Létező JDBC DataSource, MongoDB, JMS providers, stb.
  • Saját fejleszthető (implements HealthIndicator)

Health details

.small-code-14[

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "result": 1,
        "validationQuery": "SELECT 1"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1000202039296,
        "free": 680306184192,
        "threshold": 10485760
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

]


JVM belső működés

  • Heap dump: /heapdump (bináris állomány)
  • Thread dump: /threaddump

Spring belső működés

  • Beans: /beans
  • Conditions: /conditions
    • Autoconfiguration feltételek teljesültek-e vagy sem - ettől függ,
      milyen beanek kerültek létrehozásra
  • HTTP mappings: /mappings
    • HTTP mappings és a hozzá tartozó kiszolgáló metódusok
  • Configuration properties: /configprops

Trace

  • Ha van HttpTraceRepository az application contextben
  • Fejlesztői környezetben: InMemoryHttpTraceRepository
  • Éles környezetben: Zipkin vagy Spring Cloud Sleuth
  • Megjelenik a /httptrace endpoint

Kapcsolódó szolgáltatások
és library-k

  • /caches - Cache
  • /scheduledtasks - Ütemezett feladatok
  • /flyway - Flyway
  • /liquibase - Liquibase
  • /integrationgraph - Spring Integration
  • /sessions - Spring Session
  • /jolokia - Jolokia (JMX http-n keresztül)
  • /prometheus

Info

  • info prefixszel megadott property-k belekerülnek
# Spring Boot 2.6 óta
management.info.env.enabled=true

info.appname = employees
{"appname":"employees"}

Property

  • /env végpont - property source-ok alapján felsorolva
  • /env/info.appname - értéke, látszik, hogy melyik property source-ból jött
  • Spring Cloud Config esetén POST-ra módosítani is lehet
    (Spring Cloud Config Server használja)

JMX

  • spring.jmx.enabled hatására management endpointok exportálása MBean-ként
  • Kapcsolódás pl. JConsole-lal
  • JMX over HTTP beállítása Jolokiával
<dependency>
    <groupId>org.jolokia</groupId>
    <artifactId>jolokia-core</artifactId>
</dependency>
  • JavaScript, Java API
  • Kliens pl. a Jmx4Perl
  • Jmx4Perl Docker konténerben
docker run --rm -it jolokia/jmx4perl jmx4perl
  http://host.docker.internal:8080/actuator/jolokia
  read java.lang:type=Memory HeapMemoryUsage

12Factor: Admin processes

  • Felügyeleti, üzemeltetési folyamatok
  • Ne ad-hoc szkriptek
  • Alkalmazással együtt kerüljenek verziókezelésre, buildelésre és kiadásra
  • Preferálja a REPL (read–eval–print loop) használatát
    • Tipikusan command line
  • Megosztó, könnyen el lehet rontani

12Factor: Admin processes

  • Tipikusan máshogy kéne megoldani:
    • Adatbázis migráció
    • Ütemezett folyamatok
    • Egyszer lefutó kódok
    • Command line-ban elvégezhető feladatok
  • Megoldások:
    • Flyway, Liquibase
    • Magas szintű ütemező (pl. Quartz)
    • REST-en, MQ-n meghívható kódrészek
    • Új microservice

class: inverse, center, middle

Build és Git információk megjelenítése


Build információk megjelenítése

.small-code-14[

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>

]

A target/classes/META-INF könyvtárban build-info.properties fájl

.small-code-14[

{
    "build": {
        "artifact": "employees",
        "name": "employees",
        "time": "2022-10-05T20:50:03.216Z",
        "version": "0.0.1-SNAPSHOT",
        "group": "training"
    }
}

]


Git információk megjelenítése

<plugin>
  <groupId>pl.project13.maven</groupId>
  <artifactId>git-commit-id-plugin</artifactId>
</plugin>

A target/classes könyvtárban git.properties fájl

{
  "appname": "employees",
  "git": {
    "branch": "master",
    "commit": {
      "id": "d63acd0",
      "time": "2020-02-04T11:12:58Z"
    }
  }
}

12Factor: One Codebase,
One Application

  • A különböző környezetekre telepített példányoknál alapvető igény,
    hogy tudjuk, hogy mely verzióból készült (felületen, logban látható legyen)

class: inverse, center, middle

Naplózás


Naplózás lekérdezése és beállítása

  • /loggers
  • /logfile
### Get logger
GET http://localhost:8080/actuator/loggers/training.employees

### Set logger
POST http://localhost:8080/actuator/loggers/training.employees
Content-Type: application/json

{
  "configuredLevel": "INFO"
}

class: inverse, center, middle

Metrics


Metrics

  • /metrics végponton
  • Micrometer - application metrics facade (mint az SLF4J a naplózáshoz)
  • Több, mint 15 monitoring eszközhöz való csatlakozás
    (Elastic, Ganglia, Graphite, New Relic, Prometheus, stb.)

Gyűjtött értékek

  • JVM
    • Memória
    • GC
    • Szálak
    • Betöltött osztályok
  • CPU
  • File descriptors
  • Uptime
  • Tomcat (server.tomcat.mbeanregistry.enabled
    értéke legyen true)
  • Library-k: Spring MVC, WebFlux, Jersey, HTTP Client,
    Cache, DataSource, Hibernate, RabbitMQ
  • Stb.

Saját metrics

Counter.builder(EMPLOYEES_CREATED_COUNTER_NAME)
        .baseUnit("employees")
        .description("Number of created employees")
        .register(meterRegistry);

meterRegistry.counter(EMPLOYEES_CREATED_COUNTER_NAME).increment();

A /metrics/employees.created címen elérhető


12Factor hivatkozás: Telemetry

  • Adatok különböző kategóriákba sorolhatóak:
    • Application performance monitoring
    • Domain specifikus értékek
    • Health, logs
  • Új konténerek születnek és szűnnek meg
  • Központi eszköz

class: inverse, center, middle

Metrics Graphite monitoring eszközzel


Graphite architektúra

  • Az alkalmazás tölti fel bizonyos időközönként az adatokat

Graphite indítás

docker run
  -d  
  -p 80:80 -p 2003-2004:2003-2004 -p 2023-2024:2023-2024
  -p 8125:8125/udp -p 8126:8126
  --name graphite
  graphiteapp/graphite-statsd

Felhasználó/jelszó: root/root


Graphite integráció

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-graphite</artifactId>
</dependency>
management.metrics.export.graphite.step = 10s

class: inverse, center, middle

Metrics Prometheus monitoring eszközzel


Spring Boot alkalmazás
konfigurálása

  • io.micrometer:micrometer-registry-prometheus függőség
  • /actuator/prometheus endpoint
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

Prometheus architektúra

  • Prometheus kérdez le a megadott rendszerességgel
  • yml konfiguráció, prometheus.yml
scrape_configs:
  - job_name: employees
    metrics_path: '/actuator/prometheus'
    scrape_interval: 20s
    static_configs:
      - targets: ['host.docker.internal:8080']

Prometheus indítása

Tegyük fel, hogy a prometheus.yml a D:\data\prometheus könyvtárban van

docker run -d -p 9090:9090 -v D:\data\prometheus:/etc/prometheus
  --name prom prom/prometheus

class: inverse, center, middle

Audit events


Audit events

  • Pl. bejelentkezéssel kapcsolatos események
  • Saját események vehetőek fel
  • Kell egy AuditEventRepository implementáció,
    beépített: InMemoryAuditEventRepository
  • Megjelenik az /auditevents endpoint
applicationEventPublisher.publishEvent(
  new AuditApplicationEvent("anonymous",
    "employee_has_been_created",
      Map.of("name", command.getName())));

class: inverse, center, middle

Continuous Delivery Jenkins Pipeline-nal


Continuous Integration

  • Extreme Programming
  • Termék átadásának gyorsítására, integrációs idő csökkentésére
  • Revision control, branching csökkentése, gyakori commit, commit-onként build
  • Build folyamat automatizálása, idejének csökkentése
  • Tesztelés automatizálása, az éles (production) környezethez hasonló környezetben
  • A build eredménye mindenki számára hozzáférhető – „eltört build” fogalma
  • A build eredményének azonnali publikálása: hibák mielőbbi megtalálása

Continuous Integration előnyei

  • Integrációs problémák mielőbbi feltárása és javítása
  • Hibás teszt esetén könnyű visszaállás
  • Nem forduló kód mielőbbi feltárása és javítása
  • Konfliktusok mielőbbi feltárása és javítása
  • Minden módosítás azonnali unit tesztelése
  • Verziók azonnali elérhetősége
  • Fejlesztőknek szóló rövidebb visszajelzés

Continuous Delivery

Olyan megközelítés, melynek használatával a fejlesztés rövid ciklusokban történik, biztosítva hogy a szoftver bármelyik pillanatban kiadható

  • Minden egyes változás (commit) potenciális release
  • Build automatikus és megismételhető formában
  • Több lépésből áll: fordítás, csomagolás, tesztelés (statikus és dinamikus),
    telepítés különböző környezetekre
  • Deployment pipeline foglalja magába a lépéseket
  • Ugyanaz az artifact megy végig a pipeline-on

Jenkins

  • Automation server
  • Open source
  • Build, deploy, automatic
  • Nagyon sok plugin
  • Jenkins nodes: master és agents
  • Dockerben futtatható

Pipeline

  • Pluginek
  • Continuous delivery pipeline megvalósításához
  • Pipelines "as code"
  • DSL: Jenkinsfile
  • Stage-ek, melyek step-ekből állnak

Pull szemantika

  • CD túl sokmindenhez hozzáfér
  • A CD vége csak az artifact előállítás, de nem a telepítés
  • Környezet deklaratív leírás alapján előállítható/frissíthető legyen
  • Ha új környezetet kell előállítani, nem kell hozzá CD

Példa pipeline

Jenkinsfile tartalma:

.small-code-14[

pipeline {
   agent any

   stages {
      stage('package') {
         steps {
            git 'https://github.com/vicziain/employees'

            sh "./mvnw clean package"
         }
      }
      stage('test') {
         steps {
            sh "./mvnw verify"
         }
      }
   }
}

]


Maven Failsafe plugin

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.22.2</version>
    <executions>
    	<execution>
             <goals>
                 <goal>integration-test</goal>
             </goals>
    	</execution>
    </executions>
</plugin>

Maven wrapper

git update-index --chmod=+x mvnw
  • Letölti a Mavent
  • Maven letölti a függőségeket

Jenkins pipeline grafikusan

Jenkins pipeline


Dockerfile

FROM jenkins/jenkins:lts-jdk11
ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false

RUN jenkins-plugin-cli --plugins "git workflow-aggregator pipeline-stage-view blueocean"

Jenkins előkészítése

docker build --file Dockerfile.jenkins -t employees-jenkins .

docker run -d -p 8082:8080 --name employees-jenkins employees-jenkins

Job létrehozása

  • Új Item
  • Projektnév megadása, pl. employees
  • Pipeline
  • Pipeline/Definition Pipeline script from SCM
  • Git
  • Repository URL kitöltése, pl. https://github.com/vicziain/employees

12Factor hivatkozás: Design,
Build, Release and Run

  • Futtatható alkalmazás készítése
  • Forráskód + build = verziózott immutable artifact
  • Verziózott artifact + környezeti konfiguráció: egyedi azonosítóval rendelkező
    immutable release
  • Kezeli a függőségeket
  • Visszaállás egy előző release-re
  • Build, release, futtatás élesen elválik

12Factor hivatkozás:
Environment Parity

  • Ne legyen olyan, hogy "nálam működik"
  • Különbségek
    • Időbeli eltolódás (lassú release)
    • Személyi különbségek (fejlesztő nem lát rá az éles rendszerre),
      egy gombos deploy
    • Eszközbeli különbségek
      (pl. pehelysúlyú, embedded megoldások, pl. H2)
  • Docker segít