Xorcery uses Jetty & Jersey and mTLS implementation and JWT support

 




In order to better understand Xorcery, we are going to explain the building blocks on which the framework resides. At Xorcery we use Jetty and Jersey.


Jetty

Jetty is a lightweight web server and servlet container prepared to be embedded in our applications. The distribution we use is from Eclipse and has the following characteristics:


  • It is an asynchronous HTTP server
  • It is a standard Servlet Container
  • It is a Web Sockets server
  • It has an asynchronous HTTP client
  • Supports OSGI, JNDI, JMX, JASPI, AJP.

Jersey

Developing RESTful web services that seamlessly support exposing your data on a variety of rendering media types and abstracting the low-level details of client-server communication is not an easy task without a good set of tools. To simplify the development of RESTful web services and their clients in Java, a standard and portable JAX-RS API has been designed.

The Jersey RESTful Web Services 2.x framework is an open-source, production-quality framework for developing RESTful web services in Java that provides support for JAX-RS APIs and serves as a JAX-RS reference implementation (JSR 311, JSR 339, and JSR 370).

The Jersey RESTful Web Services 3.x framework provides support for Jakarta RESTful Web Services 3.0.

The Jersey framework is more than the JAX-RS reference implementation. Jersey provides its own API that extends the JAX-RS toolkit with additional functions and utilities to further simplify RESTful serving and client development. Jersey also exposes numerous extension SPIs so that developers can extend Jersey to better meet their needs.


Módulos en Java


All code in Xorcery is developed using Java Modules. If you have problems understanding this organization available since Java 9, I recommend you see this link (you can use the subtitles in Spanish or English).


GlassFish HK2 - Dependency Injection


GlassFish HK2 3.0 API is a declarative framework for services using annotations such as Contract and Service. It is based on Jakarta Dependency Injection (DI) standard annotation. We are using it as a dependency injection framework in Jersey and enabling auto-scanning to auto-discover components marked as @Contract and @Service.



Use in Xorcery to have mTLS


In the quest to achieve mTLS encrypted server-to-server communication, TLS has been activated for HTTP 1.1 and HTTP/2 and support for client certificates to obtain mTLS support.



You must have read or known about this type of communication when using a Service Mesh. But Xorcery wants to avoid such complexity and give you the same type of secure communication between services.



For this reason, in the source code you will find:

  1. xorcery-certificates: the code that starts the provisioning, sets up renewal scheduler, and stores cert into KeyStore etc. This can run on both client and server, and defines an SPI
  2. xorcery-certificates-client: implements SPI and delegates to remote server via HTTPS. Can optionally support HTTP01 ACME challenge
  3. xorcery-certificates-server: provides JAXRS API that clients (2.) can call, and then delegates to SPI implementations
  4. xorcery-certificates-letsencrypt: Let's Encrypt implementation of SPI, can be run in "client" or "server", both would work
  5. xorcery-certificates-ca: our own CA implementation of the SPI
We have sought to design the SPI carefully, to have a cleaner configuration and to make it possible to use Let's Encrypt and our own CA at the same time (they do different things and have advantages and disadvantages each).
They can run in standalone mode (where each server performs provisioning itself) or in client-server mode where provisioning/renewal is centralized.

Servers can now receive certificates from our own CA or Let's Encrypt, or both (which makes SNI possible).

Certificate Signing Request validation now optionally supports self-signing, so there is no need to distribute the provisioning key if you know all CSRs are coming from your own network.

JWT Authentication and Authorization


We also have a xorcery-jwt module that:

  1. Exposes a login endpoint (username/password). This way, clients will be able to authenticate.
  2. It has an SPI to allow the plugin to provide JWT Claims.

Support for Jetty contraints mappings has also been added to xorcerty-jetty-server so that authorization requirements can be added. Basically, it will check the "roles" of the JWT claims (as a series of role names) against the role requirements of a mapped route. With the latter we already have a level of authorization.

Use in clients


With everything explained above, you will now be able to configure certificate information, REST API resources, jetty server information, JWT issuer, etc. in your clients.

I take the following configuration from xorcery-examples:

application.name: "forum"
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.forum.resources.api.CommentResource
  - com.exoreaction.xorcery.examples.forum.resources.api.ForumResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostCommentsResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostsResource


dns.client.search:
  - xorcery.test
dns.client.hosts:
      _certificates._sub._https._tcp : "https://127.0.0.1"
dns.client.nameServers:
  - 127.0.0.1:8853

jetty:
  server:
    http:
      port: 8080
    ssl:
      port: 8443
    security:
      jwt:
        issuers:
          server.xorcery.test: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQ....."

# These features can be extracted into separate services
jwt.server.keys:
  - keyId: "2d3f1d1f-4038-4c01-beb7-97b260462ada"
    alg: "ES256"
    publicKey: "secret:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQ...."
    privateKey: "secret:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQ...."

dns.server.port: 8853

# Log configuration
log4j2.Configuration:
  name: Xorcery Example Forum
  status: warn
  thresholdFilter:
    level: trace
  appenders:
    Console:
      name: STDOUT
      target: SYSTEM_OUT
      PatternLayout:
        Pattern: "%d [%t] %-5level %marker %c{1.}: %msg%n%throwable"
#    Log4jPublisher:
#      name: Log4jPublisher
#      PatternLayout:
#        Pattern: "%d [%t] %-5level %marker %c{1.}: %msg%n%throwable"

  Loggers:
    logger:
      - name: org.apache.logging.log4j
        level: debug
        additivity: false
        AppenderRef:
          ref: STDOUT

      - name: com.exoreaction.xorcery.core.Xorcery
        level: debug

      - name: com.exoreaction.xorcery.service
        level: debug

      - name: com.exoreaction.xorcery.dns
        level: trace

    Root:
      level: info
      AppenderRef:
        - ref: STDOUT
 #       - ref: Log4jPublisher



In the following articles we are going to explain what other building blocks Xorcery has and why they are used to finally end up in microservices demos.

Your Pull Requests to this framework are welcome in advance.

Enjoy!






Share:

Xorcery usa Jetty & Jersey e implementación de mTLS y soporte a JWT

 


Para poder entender mejor a Xorcery vamos a explicar los bloques de construcción sobre los cuales reside el framework. En Xorcery hacemos uso de Jetty y Jersey. 

Jetty

Jetty, es un servidor web y contenedor de servlets ligero preparado para ser embebido en nuestras aplicaciones. La distribución que usamos es la de eclipse y posee las siguientes características:

  • Es un servidor HTTP asíncrono
  • Es un Contenedor de Servlet estándar
  • Es un servidor de Web Sockets
  • Posee un cliente HTTP asíncrono
  • Soporta OSGI, JNDI, JMX, JASPI, AJP.

Jersey

Desarrollar servicios web RESTful que admitan sin problemas la exposición de sus datos en una variedad de tipos de medios de representación y abstraigan los detalles de bajo nivel de la comunicación cliente-servidor no es una tarea fácil sin un buen conjunto de herramientas. Para simplificar el desarrollo de servicios web RESTful y sus clientes en Java, se ha diseñado una API JAX-RS estándar y portátil.


El framework Jersey RESTful Web Services 2.x es un marco de código abierto y de calidad de producción para desarrollar servicios web RESTful en Java que brinda soporte para las API JAX-RS y sirve como implementación de referencia JAX-RS (JSR 311, JSR 339 y JSR 370). 

El framework Jersey RESTful Web Services 3.x brinda soporte para Jakarta RESTful Web Services 3.0.

El framework Jersey es más que la implementación de referencia JAX-RS. Jersey proporciona su propia API que amplía el kit de herramientas JAX-RS con funciones y utilidades adicionales para simplificar aún más el servicio RESTful y el desarrollo de clientes. Jersey también expone numerosos SPI de extensión para que los desarrolladores puedan ampliar Jersey para satisfacer mejor sus necesidades.

Módulos en Java


Todo el código en Xorcery está desarrollado usando Java Modules. Si tienes problemas para entender esta organización disponible desde Java 9, te recomiendo ver este link (puedes usar los subtítulos en español o inglés).

En español encontré esta buena charla para que puedan entender el trabajo de un proyecto en módulos: Modularidad en Java, creación y migración de aplicaciones.

GlassFish HK2 - Inyección de Dependencias

GlassFish HK2 3.0 API, es un framework declarativo para servicios usando anotaciones como Contract y Service. Está basado en Jakarta Dependency Injection (DI) standard annotation. Lo estamos usando como framework de inyección de dependencias en Jersey y habilitando el auto-scanning para auto-descubrir los componentes marcados como @Contract y @Service. 



Uso en Xorcery para tener mTLS

En la búsqueda de conseguir comunicación encriptada mTLS de servidor a servidor, se ha activado el TLS para HTTP 1.1 y HTTP/2 y el soporte para certificados cliente y así obtener soporte mTLS. 





Este tipo de comunicación lo debe ud. Haber leído o conocido al usar un Service Mesh. Pero Xorcery, lo que desea es evitar tal complejidad y ofrecerle el mismo tipo de comunicación segura entre servicios.


Por tal motivo, en el código fuente encontrará:

  1. xorcery-certificates: el código que inicia el aprovisionamiento, configura el programador de renovación de certificados y almacena el certificado en el KeyStore, etc. Esto puede ejecutarse tanto en el cliente como en el servidor, y define un SPI
  2. xorcery-certificates-client: implementa SPI y delega al servidor remoto a través de HTTP. Opcionalmente, puede admitir el reto HTTP01 ACME
  3. xorcery-certificates-server: proporciona API JAXRS que los clientes (2.) pueden llamar y luego delega en implementaciones SPI
  4. xorcery-certificates-letsencrypt: Implementación Let's Encrypt de SPI, se puede ejecutar en "cliente" o "servidor", ambos funcionarían.
  5. xorcery-certificates-ca: nuestra propia implementación CA del SPI
Se ha buscado diseñar el SPI cuidadosamente, para tener una configuración más limpia y que haga posible el uso de Let's Encrypt y nuestro propio CA al mismo tiempo (hacen cosas diferentes y tienen ventajas y desventajas cada uno). 
Se pueden ejecutar en modo independiente (donde cada servidor realiza el aprovisionamiento por sí mismo) o en modo cliente-servidor donde el aprovisionamiento/renovación está centralizado.

Los servidores ahora pueden recibir certificados de nuestra propia CA o Let's Encrypt, o ambos (lo que hace posible el SNI).

La validación de la Solicitud de firma de certificado ahora admite opcionalmente la autofirma, por lo que no es necesario distribuir la clave de aprovisionamiento si sabe que todas las CSR provienen de su propia red.


JWT Autenticación y Autorización

También tenemos un módulo xorcery-jwt que:

  1. Expone un login endpoint (username/password). De esta manera, los clientes podrán realizar la autenticación.
  2. Tiene un SPI para permitir que el plugin provea JWT Claims.
En xorcerty-jetty-server también se ha agregado soporte para Jetty contraints mappings de manera que se pueda agregar requerimientos de autorización. Básicamente, se verificará los "roles" de los claims JWT (como una serie de nombres de roles) con los requisitos de roles de una ruta mapeada. Con esto último ya tenemos un nivel de autorización.



Uso en clientes


Con todo lo anteriormente explicado, ahora podrá configurar en sus clientes la información de certificados, recursos REST API, información del jetty server, JWT issuer, etc.

La siguiente configuración la tomo de xorcery-examples:


application.name: "forum"
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.forum.resources.api.CommentResource
  - com.exoreaction.xorcery.examples.forum.resources.api.ForumResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostCommentsResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostResource
  - com.exoreaction.xorcery.examples.forum.resources.api.PostsResource


dns.client.search:
  - xorcery.test
dns.client.hosts:
      _certificates._sub._https._tcp : "https://127.0.0.1"
dns.client.nameServers:
  - 127.0.0.1:8853

jetty:
  server:
    http:
      port: 8080
    ssl:
      port: 8443
    security:
      jwt:
        issuers:
          server.xorcery.test: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQ....."

# These features can be extracted into separate services
jwt.server.keys:
  - keyId: "2d3f1d1f-4038-4c01-beb7-97b260462ada"
    alg: "ES256"
    publicKey: "secret:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQ...."
    privateKey: "secret:MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQ...."

dns.server.port: 8853

# Log configuration
log4j2.Configuration:
  name: Xorcery Example Forum
  status: warn
  thresholdFilter:
    level: trace
  appenders:
    Console:
      name: STDOUT
      target: SYSTEM_OUT
      PatternLayout:
        Pattern: "%d [%t] %-5level %marker %c{1.}: %msg%n%throwable"
#    Log4jPublisher:
#      name: Log4jPublisher
#      PatternLayout:
#        Pattern: "%d [%t] %-5level %marker %c{1.}: %msg%n%throwable"

  Loggers:
    logger:
      - name: org.apache.logging.log4j
        level: debug
        additivity: false
        AppenderRef:
          ref: STDOUT

      - name: com.exoreaction.xorcery.core.Xorcery
        level: debug

      - name: com.exoreaction.xorcery.service
        level: debug

      - name: com.exoreaction.xorcery.dns
        level: trace

    Root:
      level: info
      AppenderRef:
        - ref: STDOUT
 #       - ref: Log4jPublisher

 

 En los siguientes artículos vamos a explicar que otros bloques de construcción posee Xorcery y el porqué de su uso para finalmente terminar en las demos de micro servicios. 

Sean bienvenidos de antemano sus Pull Requests a este framework.

Enjoy!


Jose






Share:

Xorcery is born - Build high-performance microservices

 

Using dynamic DNS for service discovery - Rickard Öberg from JavaZone on Vimeo.

 Today I come to tell you about one of the most promising projects that has begun to be cooked up with the leadership of our colleague Rickard Öberg and members of the team at exoreaction.

This project is called Xorcery. A library designed to help us grow our microservices-based solution that already implements REST API clients and servers, as well as reactive data streaming in a minimalist and simple way.

Below I share the ideas that Rickard Öberg has shared with our team.

REST APIs

Server Side Issue Review

When reviewing recent implementations of REST APIs, they comply with returning structured data, usually in JSON format. If this structure is not defined by a JSON schema, it could also be plain text. This means that clients have to do a lot of work to achieve a good integration. So using a JSON format is the key because it allows reusable components so you don't start from scratch all the time. See the JSON: API v1.1 specification, which is what Xorcery will be based on.

In most REST APIs there are no links, which results in having to implement endless URL structures in clients, and if they change, chaos ensues. It also makes it impossible for clients to deduce what possible actions can be taken since there are no links to forms allowed given the current state of the system.

Finally, we do not have forms as a consequence of not having links in the transmitted data. If we had forms, clients could know what actions were possible, but without them, we end up in simplistic CRUD implementations of the API, where the only way to discover that an action was not possible is to send a POST request and reject it.

Having forms bound to the data structure would allow clients to not only know when certain actions are possible but would also make it easier to perform actions that update just a few fields in a resource, rather than having to rely on PATCH requests where the client “knows” what to do.

Conclusion: The server side of REST APIs is a complete mess right now. This could be solved by simply using a media format that includes all of these features natively, such as the JSON: API format, combined with the JSON schema for definitions of the custom parts of the API.

Solution: Create a fully automated sandbox client that translates a REST API to HTML and allows a developer to interact with it in a browser.


Client Side Problem Review 

Most REST API clients are request-oriented. You construct a URL, do a GET or POST based on the API documentation, and then submit it. And since each API has its JSON structure, each client is unique and must adapt to the service it is creating. This style of clients completely ignores what we learned from the web when it comes to client design.

REST makes the server stateless, in the sense of connection sessions, but it does so by putting this responsibility in the hands of the client. But if all you have is a request-based REST client, where's the state? It is forgotten.

The correct way to do this is to view each interaction of a REST API as a session, with state. Just like a web browser does. A REST client session should allow three main actions:

  • Extract information from structured data
  • Follow links
  • Submit Forms

This makes it easier to handle error recovery, perform multiple steps, chain process flows, and handle the case where the interaction may never finish, in case the server is unavailable for the entire life of the server. customer.

These are some of the problems with REST APIs, both on the client and on the server, that we want to solve with Xorcery, making it easier to create REST APIs based on web principles and making it easier to create REST clients that have stateful sessions that can address the fallacies of distributed computing in a repeatable and natural way.

Distributed discovery, orchestration and connectivity


At the beginning of this post you will find a video of Rickard's talk at JavaZone 2023 on Using dynamic DNS for service discovery - Rickard Öberg that explains this point.

A second area that needs help is service collaboration. Services must be able to discover each other and discover where a particular service that is needed is available, using the relationships defined in the service description and JSON;API and JSON Schema and then interact with them.

If we want to scale a server up or down, we must be able to do so without an external mechanism. It must be integrated into each server so that services can detect each other, decide how to organize collaborations, and manage connections between services.

It is important to make the distinction between servers, containers and services that perform a particular task. A server can have one or twenty services, and the reason for doing one or the other should be based on needs, not a pre-built design diagram that may or may not be applicable to the running system. If a server can run all services, we usually call it a "monolith". In Xorcery it's perhaps best thought of as “a system that hasn't had enough pressure yet to require a split.”

As long as services are designed in such a way that they are not coded with a dependency on whether or not other services are located on the same server, we are free to choose when to make such splits.

While it is possible to always run servers with a single service each, each with its own Docker container running in the same virtual machine, this is not particularly cost-effective or practical. It is unnecessary complexity.

By carefully creating discovery and orchestration methods, which use REST principles to allow ignoring the physical locations of servers, we can make it easier to start small and grow when necessary as pressures on the system increase. , instead of having to design these delineations in advance.

Finally, analyzing that the main means of communication between services currently is, through REST APIs, in some situations, they are not so useful. Particularly when it comes to sending data streams to each other. There it is better to use a form of transmission abstraction and with the discovery of the reactive pattern especially the reactive flows. So, we conclude that services should be allowed to publish to reactive data streams and subscribe to and consume these streams easily. This for us is the tool we are missing.

At Xorcery we are addressing this by providing a built-in batch reactive stream feature, implemented using websockets and the Disruptor library, to make it as easy as possible to create data streams that eventually progress, can be batched, and are recoverable, in case of errors.

This is a major improvement over using REST API endpoints to transfer continuous streams of data from one service to another. Since this is a native feature, it combines well with discovery and orchestration features to allow services to easily find each other and decide who should connect to what. By using websockets and then leveraging HTTP/3, we are reusing all of the existing infrastructure we need for these connections, including SSL encryption, authentication, and authorization.


Service meshes


When a microservices system grows to "a certain size", the question will inevitably arise whether to use a service mesh or not. This is partly due to the aforementioned issues with discovery, orchestration and connectivity, but if we handle them by allowing servers to have those features natively, what could that offer us?

We are left with operational issues, mainly logging, metrics, and certificate management for SSL. Since we are dealing with Java ecosystem logging and metrics, both have tools that we can integrate with the server itself and using the reactive streams features these can be efficiently sent to centralized servers for storage and analysis.

The remaining problem related to certificate management we can handle by creating a service that runs on each Xorcery server and periodically updates the locally installed certificate to enable SSL, both for our REST APIs and websocket connections, in addition to handling some authentication and authorization where client certificates are sufficient.

With this we hope that there will be no need for cumbersome service meshes that would no longer be necessary. Fewer moving parts, fewer things to update, fewer things to fail, and fewer network connections result in fewer headaches related to the fallacies of distributed computing.

The growth of a system

The most complex part of building microservices systems has to do with dealing with growth. Or more specifically, dealing with pressure.

What pressures do we have in a distributed system that might be useful to analyze? Here are some possible options:


  • Data size
  • System Feature Size
  • Operations per second
  • Team size


For example, with small data sizes it might be sufficient to have a single server with the data. As it grows we may want to replicate it. As it grows even more, we may want to break it up.

With a small team, we may want all functions to be brought together in a single service. As the team grows or splits, we may want to split services accordingly to make change control easier to manage.

All of these things are pressures that can change the way our systems grow. But, by definition, we cannot know the future. So how would you design a system to handle these pressures without knowing what they will be? What will everything be like a year from now?, in five years, in ten years, etc.

Reading articles from Facebook and Twitter engineers about how their systems have evolved, it's clear that dealing with changing pressures is the biggest headache everyone has to deal with.

And so, the conclusion we reached is too obvious to mention. Why not design the system so that it can cope with these pressures when they occur? Instead of trying to design from the beginning what we think they will be in the future, we could implement mechanisms that allow us to change the structure of the system on the fly, in reaction to these pressures rather than proactively. Even if we don't know what the future holds, then we will know that we have options for how to deal with the various possibilities when they present themselves in front of us.

It is my hope that by addressing all of the above issues intelligently, the ability to react to pressures becomes a natural outcome and not something that needs to be specifically addressed. This is the design goal of all other parts: to make it easy to use Xorcery from the beginning to the end of the lifecycle of a microservices architecture.

In the following articles I will share with all of you implementation details and tutorials on how to use Xorcery.

Enjoy!

José Díaz


Share:

El nacimiento de Xorcery - Construir microservicios altamente performantes (1ra. Parte)

 

Using dynamic DNS for service discovery - Rickard Öberg from JavaZone on Vimeo.


Hoy vengo a contarles uno de los proyectos mas prometedores que se ha empezado a cocinar en con el liderazgo de nuestro compañero Rickard Öberg y miembros del team en exoreaction.

Este proyecto se llama Xorcery. Una librería diseñada para ayudarnos a crecer nuestra solución basada en micro servicios que ya implementa clientes REST API y servidores, así como el streaming de data reactiva de forma minimalista y simple.


A continuación comparto las ideas que ha compartido con nuestro team Rickard Öberg.


Las APIs REST

Revisión del Problema del Lado del servidor 


Al revisar las implementaciones recientes de API REST, estas cumplen con devolver datos estructurados y por lo general, en formato JSON. Y si esta estructura no esta definida por un esquema JSON, esta también podría ser un texto sin formato. Lo cual trae como consecuencia que los clientes tengan que hacer mucho trabajo para lograr una buena integración.  Asi que usar un formato JSON es la clave, porque permite componentes reutilizables para no comenzar desde cero todo el tiempo. Vease la especificación JSON:API v1.1 que es en la que Xorcery se va a basar.

En la mayoría de las API REST no hay enlaces, lo que trae como consecuencia, el tener que implementar interminables estructuras de URL en los clientes, y si estos cambian se produce el caos. También hace imposible que los clientes puedan deducir que posibles acciones se pueden seguir, ya que no hay enlaces a formularios permitidos dado el estado actual del sistema.

Con lo anterior, se puede observar la falta de "descubribilidad" que es uno de los usos claves usar REST. El permitir que un cliente pueda cuando algo es posible o no basandose en la existencia de enlaces con relaciones conocidas. 

Finalmente, no tenemos formularios como consecuencia de no tener enlaces en los datos transmitidos. Si tuvieramos formularios, los clientes podrían saber que acciones son posibles, pero, al no tenerlas teerminamos en implementaciones CRUD del API simplistas, donde la unica forma de descubrir que una acción no fue posible es enviar una solicitud POST y rechazarla. 

Tener formularios vinculados a la estructura de datos permitiría a los clientes no solo saber cuándo son posibles ciertas acciones, sino que también facilitaría la realización de acciones que actualicen solo unos pocos campos en un recurso, en lugar de tener que depender de solicitudes PATCH donde el cliente "sabe” qué hacer.

Conclusión: El lado del servidor de las API REST es un completo desastre en este momento. Esto se podría solucionar simplemente usando un formato multimedia que incluya todas estas características de forma nativa, como el formato JSON:API, combinado con el esquema JSON para definiciones de las partes personalizadas de la API.

Solución: Crear un cliente sandbox totalmente automatizado que traduzca una API REST a HTML y permita a un desarrollador interactuar con ella en un navegador. 


Revisión del Problema del Lado del cliente 

La mayoría de los clientes de API REST estan orientados a solicitudes. Usted construye una URL, hace un GET o POST basado en la documentación del API y luego la envía. Y como cada API tiene su estructura JSON cada cliente es único y debe adaptarse al servicio que está creando. Este estilo de clientes ignora por completo lo que aprendimos de la web cuando se trata de diseño de clientes. 

REST hace que el servidor sea sin estado, en el sentido de sesiones de conexión, pero lo hace poniendo esta responsabilidad en manos del cliente. Pero si todo lo que tiene es un cliente REST basado en solicitudes, ¿dónde está el estado? Esta olvidado.

La forma correcta de hacerlo es ver cada interacción de una API REST como una sesión, con estado. Tal como lo hace un navegador web. Una sesión de cliente REST debería permitir tres acciones principales:

  • Extraer información de los datos estructurados
  • Seguir enlaces
  • Enviar Formularios
Esto hace que sea más fácil manejar la recuperación de errores, realizar múltiples pasos, encadenar flujos de procesos y manejar el caso en el que es posible que la interacción nunca termine, en caso de que el servidor no esté disponible durante toda la vida útil del cliente.

Estos son algunos de los problemas de las API REST, tanto en el cliente como en el servidor, que queremos solucionar con Xorcery, facilitando la creación de API REST basadas en los principios de la web y facilitando en crear clientes REST que tengan sesiones con estado que puedan abordar las falacias de la computación distribuida de una manera repetible y natural.


Descubrimiento distribuido, orquestación y conectividad


Al comienzo de este post encontrará un vídeo de la charla de Rickard en el JavaZone del 2023 sobre como Using dynamic DNS for service discovery - Rickard Öberg que explica este punto.

Una segunda area que necesita ayuda es la colaboración de servicios. Los servicios deben poder descubrirse entre sí y descubrir donde está disponible un servicio en particular que se necesita, mediante las relaciones definidas en la descripción del servicio y JSON;API y JSON Schema para luego interactuar con ellos. 

Si queremos escalar un servidor para arriba o para abajo, debemos poder hacerlo sin un mecanismo externo. Debe estar intergrado en cada servidor para que los servicios puedan detectarse entre sí, decidir como organizar las colaboraciones y gestionar las conexiones entre servicios. 

Es importante, hacer la distinción entre servidores, contenedores y servicios que realizar una tarea particular. Un servidor puede tener uno o veinte servicios, y la razón para hacer uno u otro debe basarse en las necesidades, no en un diagrama de diseño preconstruido que puede o no ser aplicable al sistema en ejecución. Si un servidor puede ejecutar todos los servicios, normalmente lo llamamos "monolito". En Xorcery tal vez sea mejor considerarlo como “un sistema que aún no ha tenido suficiente presión como para requerir una división”.


Siempre que los servicios estén diseñados de tal manera que no estén codificados con la dependencia de que otros servicios se ubiquen o no en el mismo servidor, tenemos la libertad de elegir cuándo realizar dichas divisiones. 
Si bien es posible ejecutar siempre servidores con un solo servicio cada uno, pero, cada uno con su propio contenedor Docker ejecutándose en la misma máquina virtual, esto no es particularmente rentable ni práctico. Es una complejidad innecesaria.

Al crear cuidadosamente métodos de descubrimiento y orquestación, que utilizan los principios de REST para permitir ignorar las ubicaciones físicas de los servidores, podemos hacer que sea más fácil comenzar poco a poco y crecer cuando sea necesario, a medida que aumentan las presiones sobre el sistema, en lugar de tener que diseñar por adelantado estas delineaciones.

Finalmente, analizando que el principal medio de comunicación entre servicios actualmente es, a traves, de API REST, en algunas situaciones, no son tan útiles. En particular cuando se trata de enviar flujos de datos entre sí. Ahí es mejor utilizar una forma de abstracción de transmisión y con el descubrimiento del patrón reactivo en especial los flujos reactivos. Entonces, concluimos que se debe permitir a los servicios publicar en flujos de datos reactivos y se suscriban y consuman estos flujos facilmente. Esto para nosotros es la herramienta que nos falta. 

En Xorcery estamos abordando esto proporcionando una función incorporada de flujo reactivo de procesamiento por lotes, que se implementa mediante websockets y la biblioteca Disruptor, para que sea más fácil posible la creación de flujos de datos que eventualmente progresen, que puedan procesarse por lotes y sean recuperables, en caso de errores. 

Esta es una mejora importante con respecto al uso de endpoints de API REST para transferir flujos continuos de datos de un servicio a otro. Como se trata de una característica nativa, combina bien con las características de descubrimiento y orquestación para permitir que los servicios se encuentren fácilmente entre sí y decidan quién debe conectarse a qué. Al utilizar websockets y, posteriormente, aprovechar el HTTP/3, estamos reutilizando toda la infraestructura existente que necesitamos para estas conexiones, incluido el cifrado, la autenticación y la autorización SSL.

Service meshes (Mallas de Servicios)


Cuando un sistema de microservicios crece hasta "un cierto tamaño", inevitablemente llegará la pregunta de si se debe utilizar una malla de servicios o no. Esto se debe en parte a los problemas mencionados anteriormente con el descubrimiento, la orquestación y la conectividad, pero si los manejamos permitiendo que los servidores tengan esas características de forma nativa, ¿qué nos podría ofrecer?

Nos quedan problemas operativos, principalmente de registro, métricas y gestión de certificados para SSL. Dado que estamos tratando con el registro y las métricas del ecosistema Java, ambos tienen herramientas que podemos integrar con el servidor mismo y, utilizando las funciones de flujos reactivos, estos se pueden enviar de manera eficiente a servidores centralizados para su almacenamiento y análisis.

El problema restante relacionado con la administración de certificados lo podemos manejar creando un servicio que se ejecute en cada servidor Xorcery y que actualice periódicamente el certificado instalado localmente para habilitar SSL, tanto para nuestras API REST como para conexiones websocket, además de manejar algunos escenarios de autenticación y autorización donde los certificados de cliente son suficientes.

Con esto esperamos que no haya necesidad de los engorrosos service meshes que ya no serían necesarios. Menos piezas móviles, menos cosas que actualizar, menos cosas que puedan fallar y menos conexiones de red dan como resultado menos dolores de cabeza relacionados con las falacias de la computación distribuida.



El crecimiento de un sistema

La parte más compleja de la creación de sistemas de microservicios tiene que ver con afrontar el crecimiento. O más específicamente, lidiar con la presión.

¿Qué presiones tenemos en un sistema distribuido que podría ser útil analizar? Aquí hay algunas opciones posibles:

  • Tamaño de la data
  • Tamaño de la característica del sistema
  • Operaciones por segundo
  • Tamaño del equipo

Por ejemplo, con tamaños de datos pequeños podría ser suficiente tener un único servidor con los datos. A medida que crezca es posible que queramos replicarlo. A medida que crezca aún más, es posible que queramos fragmentarlo.

Con un equipo pequeño, es posible que queramos que todas las funciones se reúnan en un solo servicio. A medida que el equipo crece o se divide, es posible que deseemos dividir los servicios en consecuencia para facilitar la gestión del control de cambios.

Todas estas cosas son presiones que pueden cambiar la forma en que crecen nuestros sistemas. Pero, por definición, no podemos conocer el futuro. Entonces, ¿cómo diseñaría un sistema para manejar estas presiones sin saber cuáles serán? Cómo será todo de aquí a un año?, en cinco años, en diez años, etc. 

Al leer artículos de ingenieros de Facebook y Twitter sobre cómo han evolucionado sus sistemas, queda claro que lidiar con los cambios en las presiones es el principal dolor de cabeza con el que todos tienen que lidiar.

Y así, la conclusión a la que llegamos, resulta tan obvia para mencionarla. ¿Por qué no diseñar el sistema de manera que pueda hacer frente a estas presiones cuando se produzcan? En lugar de intentar diseñar desde el principio lo que creemos que serán en el futuro, podríamos implementar mecanismos que nos permitan cambiar la estructura del sistema sobre la marcha, como reacción a estas presiones y no de manera proactiva. Aunque no sepamos lo que nos deparará el futuro, entonces sabremos que tenemos opciones sobre cómo lidiar con las diversas posibilidades cuando se presenten frente a nosotros.

Tengo la esperanza de que, al abordar todas las cuestiones antes mencionadas de manera inteligente, la capacidad de reaccionar ante las presiones se convierta en un resultado natural y no en algo que deba abordarse específicamente. Este es el objetivo de diseño de todas las demás partes: facilitar el uso de Xorcery desde el principio hasta el final del ciclo de vida de una arquitectura de microservicios.


En los siguientes articulos ire compartiendo con todos ustedes detalles de la implementación y tutoriales de como usar Xorcery.


Enjoy!


José Díaz



Share:

GitHub API for Java

 


This library defines an object oriented representation of the GitHub API. By "object oriented" we mean there are classes that correspond to the domain model of GitHub (such as GHUser and GHRepository), operations that act on them as defined as methods (such as GHUser.follow()), and those object references are used in favor of using string handle (such as GHUser.isMemberOf(GHOrganization) instead of GHUser.isMemberOf(String))

This is the GitHub repo: https://github.com/hub4j/github-api.

In a recent project, I used this library and I promised to make a post about it because I see that there are few examples in the documentation.

So I am sharing with you the experience I had with this library:


1. One of the ways to connect to a repository (public or private) is to use your personal token.

        @Value("${github.personaltoken}")

        String githubPersonalToken;


        @Value("${github.repo.skill.organization}")

        String githubRepoSkillOrganization;


        @Value("${github.repo.skill.target}")

        String githubRepoSkillTarget;  


        GitHub github = new GitHubBuilder().withOAuthToken(githubPersonalToken,

                        githubRepoSkillOrganization).build();


        GHRepository repo = github.getRepository(githubRepoSkillOrganization + "/" +       githubRepoSkillTarget);


        GHContentBuilder ghContentBuilder = null;

        GHContentUpdateResponse ghContentUpdateResponse = null;


2. If you are only interested in committing new files, this code will help you. It fails if the file exists on the destination.


        try {


          //Get DTO object

          Skill skill = skillConverter.entityToDto(optionalSkillEntity.get(), true);

          ObjectWriter ow = new ObjectMapper().registerModule(new JavaTimeModule()).writer().withDefaultPrettyPrinter();

          //Create file with the structure of DTO in json format

          fileName = skill.getName() + "-" + skill.getId() +".json";

          Path jsonPath = Paths.get("json/"+fileName);

          ow.writeValue(jsonPath.toFile(), skill);


          //Get contents of json file

          byte[] fileContent = Files.readAllBytes(jsonPath);

          DateTimeFormatter formatterLocalDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");


          String message = "Public skill definitions: " + skill.getName() + " with owner: "

              + skill.getSkillOwner()

              + " was  edited by " + skill.getEditedByUsername() + " on " +

              formatterLocalDateTime.format(skill.getLastEdited());

          //Commit to Repo

          if(!testMode) {

            ghContentBuilder = repo.createContent();

            ghContentUpdateResponse = ghContentBuilder.content(fileContent).path(fileName)

                .message(message).commit();

            log.info("ghContentUpdateResponse: " +ghContentUpdateResponse);

          }else{

            log.info(message);

          }



          //...


        } catch (JsonProcessingException e) {

          throw new RuntimeException(e);

        } catch (IOException e) {

          throw new RuntimeException(e);

        }




I tested with 500 files and it created them in the destination repository without problems. But, as I mentioned, it fails if the file exists. Below I show you a code that works in both cases (create/update).


3. In this new version, creation and updating is guaranteed.


           try {


                                //This sleep guarantees more files committed

                Thread.sleep(1000); 


                Optional<SkillEntity> optionalSkillEntity = skillRepository.findById(

                        skillStatusEntity.getSkillId());


                if (optionalSkillEntity.isPresent()) {


                    try {


                        //Get DTO Object

                        Skill skill = skillConverter.entityToDto(optionalSkillEntity.get(), true);

                        ObjectWriter ow = new ObjectMapper().registerModule(new JavaTimeModule()).writer().withDefaultPrettyPrinter();

                        //Get file name and fill it with DTO in json format

                        fileName = skill.getName() + "-" + skill.getId() + ".json";

                        Path jsonPath = Paths.get("json/" + fileName);

                        ow.writeValue(jsonPath.toFile(), skill);


                        DateTimeFormatter formatterLocalDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");


                        String message = "Public skill definitions: " + skill.getName() + " with owner: "

                                + skill.getSkillOwnerUsername()

                                + " was edited by " + skill.getEditedByUsername() + " on " +

                                formatterLocalDateTime.format(skill.getLastEdited());

                        //commit to Repo

                        if (!testMode) {

                            try {

                                String filePath = "quadim-public-skilldefinitions/" + fileName;

                                log.debug("Commiting public skill definition to Github: {0}", filePath);


                                GitHub github = new GitHubBuilder().withOAuthToken(githubPersonalToken,

                                        githubRepoSkillOrganization).build();

                                GHRepository repo = github.getRepository(githubRepoSkillOrganization + "/" + githubRepoSkillTarget);


                                // get the reference to the main branch

                                GHRef masterRef = repo.getRef("heads/main");

                                // get the SHA of the latest commit on the master branch

                                String masterTreeSha = repo

                                        .getTreeRecursive("main", 1)

                                        .getSha();


                                // create a tree with our entry and get its SHA

                                String treeSha = repo.createTree()

                                        .baseTree(masterTreeSha)

                                        .textEntry(filePath, FileUtils.readFileToString(jsonPath.toFile()), false)

                                        .create().getSha();


                                //create a commit for the new tree and get its SHA

                                String commitSha = repo.createCommit()

                                        .message(message)

                                        .tree(treeSha)

                                        .parent(masterRef.getObject().getSha())

                                        .create().getSHA1();

                                // Update the master reference to refer to the new commit

                                masterRef.updateTo(commitSha);


                                //....

                            } catch (IOException e) {

                                log.error("Error Committing public skill definition to Github ", e.getMessage());

                            }

                        } else {

                            log.debug("We only committed public skill definitions to PROD: " + message);

                            updateSkillStatusByGithubStatus(skillStatusEntity, GithubStatus.COMMITTED);

                        }


                    } catch (JsonProcessingException e) {

                        log.error("Error processing json file ", e.getMessage());

                    }

                }


             

            } catch (Exception e) {

                log.error("Trouble handling a skill-definition", e);

            }

        }



Congratulations to Kohsuke Kawaguchi for this great job and contributors around the world.


Enjoy!


Joe




Share: