Události v aplikaci - I

21. 12. 2021 0:00 Jiří Raška

V jednom z dřívějších článků zabývajících se službami v distribuovaných systémech jsem se přiznal, že jsem silným zastáncem auditních záznamů o běhu aplikací. Auditními záznamy v tomto smyslu rozumím informace o událostech, které se staly při běhu aplikace. Takovými záznamy mohu sledovat běžný provoz aplikace ale také anomálie, které při provozu nastaly.

Pokud jste tvůrci aplikací v jazyce Java a používáte framework Spring (v mém případě jeho rozšíření Spring Boot ver. 2.6.1), pak by se vám mohla hodit podpora pro vytváření a sledování aplikačních událostí Standard and Custom Eventszabudovaná rovnou do jádra framework.

Jako ukázku a případnou motivaci pro jejich použití jsem se rozhodl napsat následující řádky.

Při prezentaci jednotlivých řešení bych se rád přiblížil reálnému použití. Proto je výsledkem mého snažení aplikace ve Spring Boot, která po spuštění představuje jednu instanci informačního systému. Prezentované funkce jsou obvykle realizovány formou profilů, takže je možné při spuštění zapnout pouze tu funkcionalitu, která vás aktuálně zajímá.

Technický úvod

Všechny zdrojové kódy související s tímto textem můžete najít na GitHub v projektu jraska1/jv-application-events-guide.

Články připravuji ve virtualizovaném linuxovém prostředí. Používám toto, i když na vlastní funkčnost projektu by to nemělo mít vliv:

  • VirtualBox 6.1 jako virtuální prostředí

  • Fedora 35

  • OpenJDK 11 z distribuce

  • Maven 3 z distribuce

Pro experimentování s REST rozhraním používám ještě nástroje pro práci s JSON datovým formátem, přesněji:

  • jq pro zobrazení a manipulaci s existujícím JSON daty

  • jo pro generování nových JSON dat (jako požadavek na službu)

Podrobnější informace pro nastavení testovacího prostředí

Pro ty z vás, kteří potřebujete pomoc s nastavením prostředí a spouštěním jednotlivých příkladů, připojuji detailnější postup.

Ověřte si, že máte dostupné JDK:

[raska@fedora ~]$ java -version
openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment 18.9 (build 11.0.13+8)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8, mixed mode, sharing)

A také nainstalovaný Maven:

raska@fedora ~]$ mvn -version
Apache Maven 3.6.3 (Red Hat 3.6.3-13)
Maven home: /usr/share/maven
Java version: 11.0.13, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-2.fc35.x86_64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.15.5-200.fc35.x86_64", arch: "amd64", family: "unix"

Příklady vyvíjím a testuji ve vývojovém prostředí IntelliJ IDEA. Do tohoto prostředí si můžete stáhnout projekt rovnou z GitHub a sestavit jej.

Pokud se nechcete zabývat vývojovým prostředím, pak si jej můžete sestavit z příkazové řádky.

Nejjednodušší postup je následující:

[raska@fedora ~]$ mkdir devel && cd devel
[raska@fedora devel]$ git clone https://github.com/jraska1/jv-application-events-guide.git
[raska@fedora devel]$ cd jv-application-events-guide/
[raska@fedora jv-application-events-guide]$ mvn package

Spustit aplikaci můžete tak, že zavoláte rovnou sestavený JAR:

[raska@fedora jv-application-events-guide]$ java -jar target/application-events-guide-1.0.0.jar

Aplikace

Jedná se o standardní aplikaci napsanou pro framework Spring Boot. Její sestavení a základní komponenty jsou popsány v rámci Maven projektového souboru pom.xml.

Samotná aplikace po svém startu zahrnuje:

  • Webový server Tomcat včetně podpory pro vytváření webových služeb. V mém případě jej budu používat pro uživatelský přístup prostřednictvím REST volání.

  • H2relační databáze s daty primárně ukládanými za běhu v paměti. Tu používám pouze při ukázce dlouhodobého ukládání informací o událostech do relační databáze.

Konfigurace aplikace

Základní konfigurace platná pro každou spuštěnou instanci aplikace je v souboru resources/application.yaml. Jedná se o konfigurační soubor ve formátu YAML, který se načítá jako první při spuštění aplikace.

Takto vypadá základní konfigurační soubor:

spring:
  profiles:
    active:
node:
  name: node01
  id: local:${node.name}

Ve výchozí konfiguraci nemá aplikace přiřazen žádný aktivní profil. Později uvidíte, že v tomto případě jsou k dispozici dvě jednoduché aplikační služby dostupné přes REST rozhraní. Kromě těchto dvou služeb již nic dalšího aplikace neumí, ale i tak se dá používat.

Parametry node.name a node.id pouze zavádění identifikační údaje pro pojmenování spuštěné aplikace. Ty se budou objevovat v odpovědích aplikačních služeb.

Pokud se podíváte do resources projektu, pak zde kromě základního konfiguračního souboru aplikace uvidíte také konfigurační soubory pro jednotlivé profily nazvané application-<název profilu>.yaml. Ty Spring Boot načítá následně pro hlavním konfiguračním souboru pro každý aktivní profil. Parametry zde nastavené mají přednost před parametry z hlavního souboru.

Například pro profil listener-couchdb existuje konfigurační soubor application-listener-couchdb.yaml s parametry potřebnými pro připojení na CouchDB:

couchdb:
  url-base: http://localhost:5984/events
  username: admin
  password: admin

Hlavní třída aplikace

V rámci projektu je pouze jedna třída, která má statickou metodu main(), a sice Application.

@SpringBootApplication
public class Application {

    private static final Logger logger = LoggerFactory.getLogger(Application.class);

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

    @Bean
    public ApplicationRunner applicationRunner(@Nullable EventQueueComponent eventQueueComponent, @Nullable DelayQueue<DelayedCustomEvent<? extends CustomEvent>> eventQueue) {
        return args -> {
            logger.info("*** Hello World, greetings from Dwarf ***");
            if (eventQueueComponent != null)
                eventQueueComponent.runEventConsumer(eventQueue);
        };
    }
}

Toto je základní rámec pro spuštění aplikace. Po inicializaci frameworku je ještě zavolána metoda applicationRunner(). V tomto okamžiku se nemusíte zabývat tím, k čemu jsou tam parametry eventQueueComponent a eventQueue. K nim se dostanu v některé z následujících kapitol.

Úspěšné spuštění aplikace by mělo být završeno pozdravem pro celý svět od autora.

Spuštění jedné instance

Jak jsem uvedl již dříve, mám pouze jednu aplikaci, kterou chci spouštět s různými rolemi. Na to používám samostatný terminál pro každou instanci. Jako parametr spuštění se zadávají názvy aktivních profilů, které má aplikace spustit.

Tak například, spuštění instance s profily listener-simple a event-checker se provede následovně:

[raska@fedora jv-application-events-guide]$ java -jar target/application-events-guide-1.0.0.jar --spring.profiles.active=listener-simple,event-checker

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.1)

2021-12-03 17:52:27.431  INFO 3151 --- [           main] cz.dsw.app_events_guide.Application      : Starting Application v1.0.0 using Java 11.0.13 on fedora with PID 3151 (/home/raska/IdeaProjects/jv-application-events-guide/target/application-events-guide-1.0.0.jar started by raska in /home/raska/IdeaProjects/jv-application-events-guide)
2021-12-03 17:52:27.434  INFO 3151 --- [           main] cz.dsw.app_events_guide.Application      : The following profiles are active: listener-simple,event-checker
2021-12-03 17:52:28.717  INFO 3151 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-12-03 17:52:28.795  INFO 3151 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 31 ms. Found 0 JPA repository interfaces.
2021-12-03 17:52:29.612  INFO 3151 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-12-03 17:52:29.632  INFO 3151 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-12-03 17:52:29.632  INFO 3151 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.55]
2021-12-03 17:52:29.746  INFO 3151 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-12-03 17:52:29.747  INFO 3151 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2176 ms
2021-12-03 17:52:30.060  INFO 3151 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-12-03 17:52:30.466  INFO 3151 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-12-03 17:52:30.579  INFO 3151 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-12-03 17:52:30.721  INFO 3151 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.1.Final
2021-12-03 17:52:31.009  INFO 3151 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-12-03 17:52:31.196  INFO 3151 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2021-12-03 17:52:32.104  INFO 3151 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-12-03 17:52:32.114  INFO 3151 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-12-03 17:52:32.250  WARN 3151 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-12-03 17:52:32.729  INFO 3151 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-12-03 17:52:32.795  INFO 3151 --- [           main] cz.dsw.app_events_guide.Application      : Started Application in 6.576 seconds (JVM running for 7.41)
2021-12-03 17:52:32.798  INFO 3151 --- [           main] cz.dsw.app_events_guide.Application      : *** Hello World, greetings from Dwarf ***

Zastavení instance

Vzhledem k tomu, že mám každou instanci spuštěnou v samostatném terminálu, pro zastavení postačuje Ctrl-C.

Služby

Na tomto místě jsem převzal jako příklady služeb to, co jsem používal již v dřívějších povídáních o distribuovaných systémech.

Ono zase o obsah těch služeb v podstatě nejde. Jsou to jen jednoduché příklady, které je potřeba v reálné situaci nahradit pro vás užitečným kódem. Ale jako základ, aby se něco dělo, to bude postačovat.

Mám vytvořené dvě služby serviceA a serviceB, které obě fungují na principu požadavek – odpověď.

Pro každou službu mám tedy vytvořenu dvojici specializovaných tříd představující požadavek a k němu odpovídající odpověď. Všechny třídy jsou odvozeny od jednoho společného předka, a tím je třída Token.

Dědičný vztah tříd a jejich odpovídající vazby na služby budou asi nejlépe vidět na diagramu:

Jediným společným předkem pro všechny typy vyměňovaných zpráv je abstraktní třída Token. Atributy definované v rámci této třídy mají následující význam:

  • nid [URI] – technický identifikátor instance uzlu; používá se pro jednoznačnou identifikaci původce zprávy

  • name [String] – uživatelsky čitelný název instance uzlu; nemusí být nutně jednoznačný, neboť se nepoužívá pro identifikaci

  • tid [URI] – jednoznačný identifikátor transakce; v případě komunikace typu request/response slouží k provázání žádosti s odpověďmi

  • ts [Date] – časová značka vzniku zprávy

Dále mám definovány další následovníky:

  • Request (abstraktní) – předek pro všechny typy zpráv vystupující jako dotaz v synchronní komunikaci typu požadavek/odpověď

  • Response – předek pro typy zpráv vystupující jako odpověď na dotaz v synchronní komunikaci typu požadavek/odpověď

    • code [ResponseCodeType] – návratový kód odpovědi

No a nakonec jsou zde konkrétní třídy dotazů a odpovědí pro služby serviceA a serviceB:

  • RequestA – požadavek na službu A

  • RequestB – požadavek na službu B

  • ResponseA – výsledek služby A

  • ResponseB – výsledek služby B

Všechny definice tříd implementujících předávané zprávy jsou součástí package entity.

Definice služeb

Jako společnou definici služeb mám vytvořeno rozhraní component/ServiceProvider:

public interface ServiceProvider <R extends Request, T extends Response> {

    T perform(R request);

    default String getInstanceName() { return "Provider"; }
}

Obsahuje jednu výkonnou metodu, jejíž vstupem je požadavek odvozený od třídy Request. Výstupem je pak výsledek odvozený od třídy Response.

Dále je tam ještě metoda poskytující jméno konkrétní instance. To se mně bude hodit při rozlišování, u jakého poskytovatele vznikla událost.

Implementace služeb

V package component mám dvě implementace služeb, serviceA a serviceB. Obě jsou anotovány jako @Service, takže pro startu aplikace budu mít v kontextu k dispozici jejich instance.

Takto vypadá implementace ServiceProviderA:

@Service
public class ServiceProviderA implements ServiceProvider<RequestA, ResponseA> {

    @Autowired private TokenFactory factory;
    @Autowired private ApplicationEventPublisher publisher;

    @Override
    public ResponseA perform(RequestA request) {

        publisher.publishEvent(new ProviderEvent(this, ProviderEvent.Phase.ASKED, request));

        ResponseA response = factory.tokenInstance(request.getTid(), ResponseA.class);
        response.setCode(ResponseCodeType.OK);
        response.setResult(request.getValue() + new Random().nextInt((int) Math.max(request.getValue() / 2, 10)));

        publisher.publishEvent(new ProviderEvent(this, ProviderEvent.Phase.ANSWERED, request, response));

        return response;
    }

    @Override
    public String getInstanceName() { return "Provider A"; }
}

A takto implementace ServiceProviderB:

@Service
public class ServiceProviderB implements ServiceProvider<RequestB, ResponseB> {

    @Autowired private TokenFactory factory;
    @Autowired private ApplicationEventPublisher publisher;

    @Override
    public ResponseB perform(RequestB request) {

        publisher.publishEvent(new ProviderEvent(this, ProviderEvent.Phase.ASKED, request));

        ResponseB response = factory.tokenInstance(request.getTid(), ResponseB.class);
        response.setCode(ResponseCodeType.OK);
        response.setText("text length: " + request.getText().length());

        publisher.publishEvent(new ProviderEvent(this, ProviderEvent.Phase.ANSWERED, request, response));

        return response;
    }

    @Override
    public String getInstanceName() {return "Provider B";}
}

Obě implementace jsou triviální. Vytvoří nový token jako odpověď, nastaví návratový kód a nakonec nějaký výsledek na základě dat z požadavku.

To, co je skutečně zajímavé, je instance ApplicationEventPublisher a na ní volaná metoda publishEvent(). Ale na ty se podívám až v následujících kapitolách.

REST rozhraní služeb

Implementace služby mně nestačí. Potřebuji se k ní nějak dostat zvenčí aplikace.

Pro tento případ jsem vytvořil jednoduché REST rozhraní, pro každou službu samostatné URL.

Vstupem REST rozhraní služby je požadavek ve formě JSON objektu. Výsledek je pak opět JSON objekt odeslaný žadateli na výstupu rozhraní.

Rozhraní je implementováno třídou ProviderController v package rest.

@RestController
public class ProviderController {

    @Autowired private ServiceProvider<RequestA, ResponseA> providerA;
    @Autowired private ServiceProvider<RequestB, ResponseB> providerB;

    @RequestMapping(value = "/rest/serviceA", method = {RequestMethod.GET, RequestMethod.POST})
    public ResponseA restProviderA(@RequestBody RequestA request) {
        return providerA.perform(request);
    }

    @RequestMapping(value = "/rest/serviceB", method = {RequestMethod.GET, RequestMethod.POST})
    public ResponseB restProviderB(@RequestBody RequestB request) {
        return providerB.perform(request);
    }
}

Implementace je skutečně jednoduchá, většinu práce při konverzi JSON objektu na Java Bean a zpět provede Spring framework sám. Pro toto povídání je podstatné injektování instancí poskytovatelů služby ze Spring kontextu.

Služby jsou dostupné na URL /rest/serviceA resp. /rest/serviceB. Nezapomeňte, že při vyvolání služby musí být nastavena HTTP hlavička Content-Type: application/json, aby rozhraní poznalo, že na vstupu je ten správný obsah.

Ověření funkce služeb

Teď již máme vše, abychom mohli služby vyzkoušet.

Nejjednodušší cesta je použití řádkového příkazu curl pro vytvoření HTTP dotazu.

Nejdříve potřebujeme spustit instanci aplikace. Bude nám postačovat default profil, takže v samostatném terminálu:

[raska@fedora jv-application-events-guide]$ java -jar target/application-events-guide-1.0.0.jar

Měla by se vám rozběhnout aplikace a zůstat aktivní.

Dále ve druhém terminálu zkusím vyvolat službu A:

[raska@fedora ~]$ jo -p nid=local:node02 name=node02 tid=${RANDOM} value=20 | curl -s -X GET --data-binary @- -H "Content-type: application/json" http://localhost:8080/rest/serviceA | jq .
{
  "nid": "local:node01",
  "name": "node01",
  "tid": "8085",
  "ts": "2021-12-05T10:36:40.371+00:00",
  "code": "OK",
  "result": 24
}

Nejdříve jsem si vytvořil JSON objekt dotazu pomocí příkazu jo a poslal jej na standardní výstup. Dále jsem v rouře zavolal curl s požadavkem na standardním vstupu (to je ten parametr -d), nastavenou hlavičkou Content-Type a odpovídajícím URL. Výsledek je naformátován pomocí příkazu jq (to je jen pro přehlednější zobrazení).

Pokud si nejste jisti, co vytvořil příkaz jo, pak si to můžete ověřit:

[raska@fedora ~]$ jo -p nid=local:node02 name=node02 tid=${RANDOM} value=20 | jq .
{
  "nid": "local:node02",
  "name": "node02",
  "tid": 16950,
  "value": 20
}

A stejným postupem si můžu vyzkoušet službu B:

[raska@fedora ~]$ jo -p nid=local:node03 name=node03 tid=$RANDOM text=blablabla | curl -s -X GET --data-binary @- -H "Content-type: application/json" http://localhost:8080/rest/serviceB | jq .
{
  "nid": "local:node01",
  "name": "node01",
  "tid": "5073",
  "ts": "2021-12-05T10:45:07.248+00:00",
  "code": "OK",
  "text": "text length: 9"
}

Klient pro vyvolání služeb

V předchozím kapitole jsem volal služby příkazem z příkazového řádku.

Pro testování by se ale někdy mohlo hodit, abych měl nějakého klienta, který sám bude dráždit rozhraní a vytvářet události. Takže jsem jednoho takového udělal. Dá se spustit s profilem applicant.

Takto vypadá specifický konfigurační soubor pro tuto roli application-applicant.yaml:

spring:
  main:
    web-application-type: NONE

node:
  name: node09
  id: local:${node.name}

applicant:
  resource-base: http://localhost:8080/rest

Pokud budu spouštět aplikaci s tímto profilem, pak nebudu chtít, aby mně běžel HTTP server (instance nebude plnit roli serveru). Proto je zde nastaven parametr spring.main.web-application-type na hodnotu NONE.

Dále je v konfiguraci nastaveno pojmenování spuštěné instance v parametru node.name.

Nakonec vidíte, že konfigurace obsahuje základní URL pro přístup k REST rozhraní služeb, což je parametr applicant.resource-base.

Implementace klienta je ve třídě component/ServiceApplicant:

@Service
@Profile("applicant")
@EnableScheduling
public class ServiceApplicant {

    private static final Logger logger = LoggerFactory.getLogger(ServiceApplicant.class);

    private final RestTemplate restTemplate;
    private static final Random rand = new Random();

    @Value("${applicant.resource-base}") private String resourceBase;

    @Autowired private TokenFactory factory;
    @Autowired private ApplicationEventPublisher publisher;

    public ServiceApplicant(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Async
    @Scheduled(fixedRate = 1000)
    public void executeApplicant() {
        if (rand.nextBoolean()) {
            RequestA request = factory.tokenInstance(RequestA.class);
            request.setValue(12345);

            publisher.publishEvent(new ApplicantEvent(this, ApplicantEvent.Phase.REQUESTED, request));

            ResponseEntity<ResponseA> response = restTemplate.postForEntity(resourceBase + "/serviceA", request, ResponseA.class);
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                logger.info("Response from service A: {}", response.getBody().getResult());
                publisher.publishEvent(new ApplicantEvent(this, ApplicantEvent.Phase.RECEIVED, request, response.getBody()));
            }
        }
        else {
            RequestB request = factory.tokenInstance(RequestB.class);
            request.setText("Haf haf haf");

            publisher.publishEvent(new ApplicantEvent(this, ApplicantEvent.Phase.REQUESTED, request));

            ResponseEntity<ResponseB> response = restTemplate.postForEntity(resourceBase + "/serviceB", request, ResponseB.class);
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                logger.info("Response from service B: {}", response.getBody().getText());
                publisher.publishEvent(new ApplicantEvent(this, ApplicantEvent.Phase.RECEIVED, request, response.getBody()));
            }
        }
    }
}

Třída má povoleno plánování pomocí anotace @EnableScheduling, což potřebuji k opakovanému spouštění metody executeApplicant() jednou za sekundu.

Metoda náhodně zvolí, zda bude volat službu A nebo B. Dále pak vytvoří požadavek na službu, který odešle do REST rozhraní.

O výsledku referuje zápisem do logu.

Vyvolání požadavku na službu a její výsledek je avizován publikování aplikační události, metodou ApplicationEventPublisher­.publishEven(). Tou se ale budu zabývat v dalších kapitolách.

Ověřit funkci služeb s klientem si mohu takto:

Nejdříve si spustím server v samostatném terminálu (ukázka již byla dříve).

V jiném terminálu si spustím klienta:

[raska@fedora jv-application-events-guide]$ java -jar target/application-events-guide-1.0.0.jar --spring.profiles.active=applicant

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.1)

2021-12-05 12:53:43.727  INFO 7618 --- [           main] cz.dsw.app_events_guide.Application      : Starting Application v1.0.0 using Java 11.0.13 on fedora with PID 7618 (/home/raska/IdeaProjects/jv-application-events-guide/target/application-events-guide-1.0.0.jar started by raska in /home/raska/IdeaProjects/jv-application-events-guide)
2021-12-05 12:53:43.736  INFO 7618 --- [           main] cz.dsw.app_events_guide.Application      : The following profiles are active: applicant
2021-12-05 12:53:44.919  INFO 7618 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-12-05 12:53:45.014  INFO 7618 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 55 ms. Found 0 JPA repository interfaces.
2021-12-05 12:53:45.919  INFO 7618 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-12-05 12:53:46.271  INFO 7618 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-12-05 12:53:46.404  INFO 7618 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-12-05 12:53:46.534  INFO 7618 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.1.Final
2021-12-05 12:53:46.810  INFO 7618 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-12-05 12:53:47.058  INFO 7618 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2021-12-05 12:53:48.080  INFO 7618 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-12-05 12:53:48.094  INFO 7618 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-12-05 12:53:48.499  INFO 7618 --- [           main] cz.dsw.app_events_guide.Application      : Started Application in 6.188 seconds (JVM running for 7.104)
2021-12-05 12:53:48.508  INFO 7618 --- [           main] cz.dsw.app_events_guide.Application      : *** Hello World, greetings from Dwarf ***
2021-12-05 12:53:48.803  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service A: 12597
2021-12-05 12:53:49.516  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service A: 14361
2021-12-05 12:53:50.504  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service B: text length: 11
2021-12-05 12:53:51.503  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service A: 13032
2021-12-05 12:53:52.524  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service A: 17072
2021-12-05 12:53:53.504  INFO 7618 --- [   scheduling-1] c.d.a.component.ServiceApplicant         : Response from service B: text length: 11
^C2021-12-05 12:53:54.432  INFO 7618 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-12-05 12:53:54.433  INFO 7618 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2021-12-05 12:53:54.488  INFO 7618 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-12-05 12:53:54.499  INFO 7618 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Takže teď již mám základ připravený a můžu se dostat k těm podstatným věcem, a těmi jsou generování událostí a reakce na ně.

Sdílet