HTTP Server For JSON Event Sourcing
May 5, 2026 ยท View on GitHub
This is a standalone HTTP server for interacting with JSON aggregates. On the write-side you can send commands, which are handled asynchronously. They are put on a Kafka command topic, which corresponds to the aggregate type in the command. This is acknowledged with a 202 HTTP status code (Accepted). Changes to aggregates come back through Server-Sent Events. This flow fits well with reactive clients.
The read-side is handled with MongoDB. You can fetch and search aggregates.
The supported paths and methods are explained in the repository pincette-jes-api.
One special path is <contextPath>/health, which just returns status code 200 (OK). This can be used for health checks.
Authentication
All requests should have a JSON Web Token, which may appear as a bearer token in the Authotrization header or the cookie named access_token. If the configuration has a public key, then the tokens will be validated. But normally you would put the service behind some gateway that handles the validation.
Authorization
The roles field in the JWT is used to determine whether the request is allowed. It is an array
of strings. A command with access control has an array of roles that can execute the command. If
the intersection of the latter and the roles field is not empty, the request can go through.
In the configuration you can have entries like authorization.<aggregate>.<command>, which
have arrays of role names as their value. These are used for the intersection. If there are no
roles for a command, then the configuration entry authorization.denyByDefault says whether the
request is allowed.
The HTTP methods GET, PUT and DELETE correspond respectively to the commands get,
put and delete. Posting a search to an aggregate also corresponds to the get command. All
other commands are posted to an individual aggregate instance.
If the data set for an aggregate is segmented across the population that can work with it, then you can use Access Control Lists in the data itself. So, you could have some high-level role in the configuration and more specific ones in the ACLs.
The ACLs also filter what is returned to the client. A search result will only contain the
aggregate instances the user is allowed to see. A GET on an instance the user cannot see will
return 404. So, ACLs completely hide aggregate instances.
Configuration
The configuration is managed by the Lightbend Config package. By default, it will try to load conf/application.conf. An alternative configuration may be loaded by adding -Dconfig.resource=myconfig.conf, where the file is also supposed to be in the conf directory. If no configuration file is available it will load a default one from the resources. The following entries are available:
| Entry | Default | Description |
|---|---|---|
| accessLog | false | A boolean indicating if access log entries should be sent to the log topic, which should be set. |
| authorization.denyByDefault | false | Commands for which there are no roles are allowed if this entry is false. |
| authorization.<aggregate>.<command> | None | The array of role names that are allowed to execute the command. |
| contextPath | /api | The URL path prefix. |
| environment | None | The name of the environment, which will be used as a suffix for the aggregates, e.g. tst, acc, etc. |
| jwtPublicKey | None | The public key string, which is used to validate all JSON Web Tokens. |
| kafka | localhost:9092 | All Kafka settings come below this entry. So for example, the setting bootstrap.servers would go to the entry kafka.bootstrap.servers. The equivalent environment variable would then be KAFKA_BOOTSTRAP_SERVERS. |
| logLevel | INFO | The log level as defined in java.util.logging.Level. |
| mongodb.database | es | The name of the MongoDB database. |
| mongodb.uri | mongodb://localhost:27017 | The URI of the MongoDB service. |
| namespace | jes-http | A name to distinguish several deployments in the same environment. |
| otlp.grpc | None | The OpenTelemetry endpoint for logs and metrics. It should be a URL like http://localhost:4317. |
| slowRequestThreshold | None | If this duration is set, then requests that take longer are logged with their request body. |
| traceSamplePercentage | 10 | The percentage of distributed trace samples that are retained. The value should be between 1 and 100. You should use the same percentage in all components that contribute to a trace, otherwise you may see incomplete traces. |
| tracesTopic | None | The Kafka topic to which event traces are sent. |
| whoami | None | An array of fields that are extracted from the JWT and put in a JSON object that becomes the value of the whoami cookie. The cookie can be used by clients to obtain basic information about the current user. |
Telemetry
A few OpenTelemetry observable counters are emitted every minute. The following table shows the counters.
| Counter | Description |
|---|---|
| http.server.average_duration_millis | The average request duration in the measured interval. |
| http.server.average_request_bytes | The average request body size in bytes in the measured interval. |
| http.server.average_response_bytes | The average response body size in bytes in the measured interval. |
| http.server.requests | The number of requests during the measured interval. |
The following attributes are added to the counters.
| Attribute | Description |
|---|---|
| aggregate | The name of the aggregate the request was about. |
| http.request.method | The request method. |
| http.response.status_code | The status code of the response. |
| instance | The UUID of the JES HTTP instance. |
The logs are also sent to the OpenTelemetry endpoint.
The event traces are JSON messages, as described in JSON Streams Telemetry. They are sent to the Kafka topic set in the tracesTopic configuration field.
Building and Running
You can build the tool with mvn clean package. This will produce a self-contained JAR-file in the target directory with the form pincette-jes-http-<version>-jar-with-dependencies.jar. You can launch this JAR with java -jar, followed by a port number.
You can run the JVM with the option -mx128m.
Docker
Docker images can be found at https://hub.docker.com/repository/docker/jsoneventsourcing/pincette-jes-http. They expose port 9000. You can either use environment variables to configure them or add a configuration layer with a Docker file that looks like this:
FROM registry.hub.docker.com/jsoneventsourcing/pincette-jes-http:<version>
COPY conf/tst.conf /conf/application.conf
So wherever your configuration file comes from, it should always end up at /conf/application.conf.
Kubernetes
You can mount the configuration in a ConfigMap and Secret combination. The ConfigMap should be mounted at /conf/application.conf. You then include the secret in the configuration from where you have mounted it. See also https://github.com/lightbend/config/blob/main/HOCON.md#include-syntax.