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á.
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)
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
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.
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
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.
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 ***
Vzhledem k tomu, že mám každou instanci spuštěnou v samostatném terminálu, pro zastavení postačuje Ctrl-C.
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.
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.
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.
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.
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" }
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ě.
Poslední dobou si dokonce pohrávám s myšlenkou, že do designu aplikace patří i dobrá debugovatelnost a testovatelnost možná víc, než samotný účel (ten vetšinou zvládne na pár řádcích každý). Já vím, nic nového pod sluncem, ale jenom málokdo tak píše kód.
Rok jsem tlačil v práci, aby jsme přešli od logování "on error only", protože některé podezřelé věci prostě error nejsou a krčit pak zpětně rameny, protože se nemohu podívat např. že komponenta nebo API nevrátila nějaký seznam, přestože měla, zatímco prázdné pole je jeden z validních výsledků, to mi nikdy nešlo pod fousy ...
Teda, logování jenom errorů si v praxi nedokážu vůbec představit. Naše aplikace produkuje za den gigabajty logů (které se sypou do Elastic Searche, odkud se po delší době zahazují). Samozřejmě po většinu času na to nikdo nekouká, ale když se něco stane (nebo když má klient podezření, že se něco stalo), tak jsme z logů schopní zjistit hrozně moc.
Já si to taky nedokážu představit. My logujeme každý příchozí request a pak i jeho result a navíc i co se dělo mezitím (jenom to důležité). Už nás to několikrát zachránilo.
No, já si to taky nedokázal před tím představit. Vlastně jsem ani nevěděl, že taková "strategie" existuje. Pak "nás" to vypeklo a už radši logujeme všechno.
Ono je to často dobré i pro to, že si třeba člověk udělá obrázek o tom, co "havárii" předcházelo. Někdy k nezaplacení. Nebo že uživatel zkouší něco udělat vícekrát, přestože tvrdí opak, etc. ...
pracuje na pozici IT architekta. Poslední roky se zaměřuje na integrační a komunikační projekty ve zdravotnictví. Mezi jeho koníčky patří také paragliding a jízda na horském kole.
Přečteno 25 734×
Přečteno 25 727×
Přečteno 25 400×
Přečteno 23 620×
Přečteno 19 356×