Vamos a explicar nuestro primer ejemplo con Xorcery llamado Greeter, que encontraréis en el siguiente repositorio de Github.
Con Xorcery podemos implementar la API de servicio como una API REST (usando JSON-API como tipo de contenido) para necesidades de request/response o websockets de transmisión reactiva (publicadores de servidores o suscriptores) para necesidades de transmisión (como abastecimiento de eventos o proyecciones o colección de logs, etcétera).
Dato importante: toda la implementación de Xorcery utiliza las API de Jakarta EE y bibliotecas importantes en el mundo Java. Xorcery, por ejemplo, utiliza HK2, un marco de inyección dinámico y liviano.
¿Por qué JSON:API?
¿Por qué Jetty WebSockets + Disruptor?
Greeter
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>17</maven.compiler.source><maven.compiler.release>17</maven.compiler.release><xorcery.version>0.79.2</xorcery.version><hk2.version>3.0.5</hk2.version><jersey.version>3.1.3</jersey.version><slf4j.version>2.0.7</slf4j.version><log4j.version>2.22.0</log4j.version><junit.version>5.10.0</junit.version><junit.platform.version>1.9.0</junit.platform.version></properties><modules><module>xorcery-examples-greeter</module></modules><dependencyManagement><dependencies><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-bom</artifactId><version>${xorcery.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-bom</artifactId><version>${log4j.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest</artifactId><version>2.2</version><scope>test</scope></dependency></dependencies></dependencyManagement>
<build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><annotationProcessorPaths><path><groupId>org.glassfish.hk2</groupId><artifactId>hk2-metadata-generator</artifactId><version>${hk2.version}</version></path></annotationProcessorPaths></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.6.1</version></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.2.2</version><dependencies><dependency><groupId>me.fabriciorby</groupId><artifactId>maven-surefire-junit5-tree-reporter</artifactId><version>1.2.1</version></dependency></dependencies><configuration><reportFormat>plain</reportFormat><consoleOutputReporter><disable>true</disable></consoleOutputReporter><statelessTestsetInfoReporterimplementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter"/></configuration></plugin></plugins></pluginManagement></build>
<repositories><repository><id>cantara-releases</id><name>Cantara Release Repository</name><url>https://mvnrepo.cantara.no/content/repositories/releases/</url></repository><repository><id>cantara-snapshots</id><name>Cantara Snapshot Repository</name><url>https://mvnrepo.cantara.no/content/repositories/snapshots/</url></repository></repositories><distributionManagement><repository><id>cantara</id><name>Cantara Release Repository</name><url>https://mvnrepo.cantara.no/content/repositories/releases/</url></repository><snapshotRepository><id>cantara</id><name>Cantara Snapshot Repository</name><url>https://mvnrepo.cantara.no/content/repositories/snapshots/</url></snapshotRepository></distributionManagement>
Implementación de Greeter
<parent><groupId>com.exoreaction.xorcery.examples</groupId><artifactId>xorcery-examples</artifactId><version>1.0-SNAPSHOT</version></parent>
<!-- Core dependencies --><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-core</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-runner</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-metadata</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-configuration-api</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-json</artifactId></dependency>
<!-- REST API --><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-jsonapi-jaxrs</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-jersey-server</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-handlebars</artifactId></dependency><dependency><groupId>com.exoreaction.xorcery</groupId><artifactId>xorcery-status-server</artifactId></dependency>
<!-- Integration -->
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-jersey-client</artifactId>
</dependency>
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-reactivestreams-client</artifactId>
</dependency>
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-reactivestreams-server</artifactId>
</dependency>
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-neo4j</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-domainevents-neo4jprojection</artifactId>
</dependency>
<!-- Logging -->
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-log4j</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-junit</artifactId>
<scope>test</scope>
</dependency>
<profiles>
<profile>
<id>jpackage</id>
<activation>
<os>
<name>linux</name>
<arch>amd64</arch>
</os>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>compile</includeScope>
<outputDirectory>${project.build.directory}/app/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-modularized-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}.jar"
tofile="${project.build.directory}/app/lib/${project.build.finalName}.jar"
overwrite="true"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-app-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/app</outputDirectory>
<resources>
<resource>
<directory>src/jpackage/app</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.akman</groupId>
<artifactId>jpackage-maven-plugin</artifactId>
<version>0.1.5</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jpackage</goal>
</goals>
<configuration>
<resourcedir>${project.basedir}/src/jpackage/resources</resourcedir>
<input>${project.build.directory}/app</input>
<mainjar>lib/${project.artifactId}-${project.version}.jar</mainjar>
<mainclass>com.exoreaction.xorcery.examples.greeter.Main</mainclass>
<name>greeter</name>
<appversion>${project.version}</appversion>
<copyright>Copyright eXOReaction AS</copyright>
<description>Description</description>
<vendor>eXOReaction AS</vendor>
<installdir>/opt/exoreaction</installdir>
<javaoptions>-Dfile.encoding=UTF-8 -Xms256m -Xmx512m</javaoptions>
<dest>${project.build.directory}/jpackage</dest>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
class GreeterResourceTest {
@RegisterExtension
static XorceryExtension xorceryExtension = XorceryExtension.xorcery()
.configuration(ConfigurationBuilder::addTestDefaults)
.addYaml(String.format("""
jetty.server.http.enabled: false
jetty.server.ssl.port: %d
""", Sockets.nextFreePort()))
.build();
@Test
void updateGreeting() throws Exception {
Configuration configuration = xorceryExtension.getServiceLocator().getService(Configuration.class);
URI baseUri = InstanceConfiguration.get(configuration).getURI();
Client client = xorceryExtension.getServiceLocator().getService(ClientBuilder.class).build();
{
String content = client.target(baseUri)
.path("/api/greeter")
.request()
.get(String.class);
System.out.println(content);
}
instance.name: "greeter"
instance.home: "{{ SYSTEM.jpackage_app-path ? jpackage.app | SYSTEM.user_dir}}"
jpackage.app: "{{ SYSTEM.jpackage_app-path }}/../../lib/app"
# So that we can generate a SSL certificate for the local hostname. Replace with whatever domain name you actually use
instance.domain: local
# Add local convenience names for your own computer into the SSL cert
certificates:
dnsNames:
- localhost
- "{{ instance.host }}"
ipAddresses:
- 127.0.0.1
- "{{ instance.ip }}"
# REST API resources
jersey.server.register:
- com.exoreaction.xorcery.examples.greeter.resources.api.GreeterResource
keystores:
enabled: true
keystore:
path: "{{ home }}/keystore.p12"
password: "password"
template: "META-INF/intermediatecakeystore.p12"
log4jpublisher.enabled: false
log4jsubscriber.enabled: false
domainevents.subscriber.configuration.projection: "greeter"
jsondomaineventprojection.enabled: false
log4j2:
Configuration: ...
@Service
@Named(GreeterApplication.SERVICE_TYPE)
public class GreeterApplication {
public static final String SERVICE_TYPE = "greeter";
private final DomainEventPublisher domainEventPublisher;
private final DomainEventMetadata domainEventMetadata;
private final GraphDatabase graphDatabase;
@Inject
public GreeterApplication(DomainEventPublisher domainEventPublisher,
GraphDatabase graphDatabase) {
this.domainEventPublisher = domainEventPublisher;
this.graphDatabase = graphDatabase;
this.domainEventMetadata = new DomainEventMetadata(new Metadata.Builder()
.add("domain", "greeter")
.build());
}
// Reads
public CompletionStage<String> get(String name) {
return graphDatabase.execute("MATCH (greeter:Greeter {id:$id}) RETURN greeter.greeting as greeting",
Map.ofEntries(entry("id", "greeter")), 30)
.thenApply(r ->
{
try (GraphResult result = r) {
return result.getResult().stream().findFirst().map(m -> m.get("greeting").toString()).orElse("Hello World");
} catch (Exception e) {
throw new CompletionException(e);
}
});
}
@Path("api/greeter")
public class GreeterResource
extends JsonApiResource {
private GreeterApplication application;
@Inject
public GreeterResource(GreeterApplication application) {
this.application = application;
}
@GET
public CompletionStage<Context> get() {
return application.get("greeting").handle((g, t)->
{
if (t != null)
{
LogManager.getLogger(getClass()).error("Could not get greeting", t);
return "";
} else
{
return g;
}
}).thenApply(Context::newContext);
}
Escritura
Nosotros ahora probaremos publicar un evento de dominio y proyectar este a neo4j.
// Writes
public CompletionStage<Metadata> handle(Record command) {
Metadata.Builder metadata = new DomainEventMetadata.Builder(new Metadata.Builder().add(domainEventMetadata.context()))
.timestamp(System.currentTimeMillis()).builder();
try {
List<DomainEvent> events = (List<DomainEvent>) getClass().getDeclaredMethod("handle", command.getClass()).invoke(this, command);
Metadata md = metadata.add(Model.Metadata.commandName, command.getClass().getName()).build();
CommandEvents commandEvents = new CommandEvents(md, events);
return domainEventPublisher.publish(commandEvents);
} catch (Throwable e) {
return CompletableFuture.failedStage(e);
}
}
private List<DomainEvent> handle(UpdateGreeting updateGreeting) {
return Collections.singletonList(JsonDomainEvent.event("UpdatedGreeting").updated("Greeter", "greeter")
.attribute("greeting", updateGreeting.newGreeting())
.build());
}
public record UpdateGreeting(String newGreeting) {
}
public class Main {
public static void main(String[] args) {
com.exoreaction.xorcery.runner.Main.main(args);
}
}
2023-11-13 12:24:18,506 [RunLevelControllerThread-1699896252353] INFO c.e.x.j.s.JettyLifecycleService: Started Jetty server
2023-11-13 12:24:18,507 [RunLevelControllerThread-1699896252353] DEBUG c.e.x.c.RunLevelLogger: Reached run level 18
2023-11-13 12:24:18,507 [RunLevelControllerThread-1699896253228] DEBUG c.e.x.c.RunLevelLogger: Reached run level 19
2023-11-13 12:24:18,508 [RunLevelControllerThread-1699896253228] DEBUG c.e.x.c.RunLevelLogger: Reached run level 20
2023-11-13 12:24:18,509 [main] DEBUG macbook-pro-de-jose.local.genericserver c.e.x.c.Xorcery: Services:
Nota: Observe cómo xorcery ejecuta los @RunLevel cuando inicia el proyecto:
Este es el sandbox HTML generado:
Si deseamos publicar el evento de dominio y proyectar este a neo4j, podemos probar con este URL http://localhost/api/greeter
0 comentarios:
Publicar un comentario