Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
guillermocalvo committed Jul 11, 2024
1 parent 16e0296 commit 5e39e6e
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 68 deletions.
111 changes: 61 additions & 50 deletions index.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
---
title: Micronaut Serialization for Result
description: Result-Micronaut-Serde provides Micronaut serialization support for Result objects
image: https://dev.leakyabstractions.com/result/result-magic-ball.png
image: https://dev.leakyabstractions.com/result/result-banner.png
---

# Micronaut Serialization for Result

This library provides [Micronaut serialization support][MICRONAUT_SERDE] for [Results objects][RESULT].
When using Result objects with [Micronaut][MICRONAUT], we might run into some problems. The
[Micronaut serialization][MICRONAUT_SERDE] support for Result solves them by making Micronaut treat results as
[`Serdeable`][SERDEABLE] (so they can be serialized and deserialized).

> [Micronaut][MICRONAUT] is a modern, JVM-based framework for building lightweight microservices and serverless
> applications. It focuses on fast startup times and low memory usage. Although not as widely adopted as
> [Spring Boot][SPRING_BOOT], it has gained popularity for its performance and innovative features.
## Introduction

When using [<tt>Result</tt> objects][RESULT_REPO] with [Micronaut][MICRONAUT], we might run into some problems.
[This library][RESULT_MICRONAUT_SERDE_REPO] solves them by making Micronaut treat results as *Serdeable* so that they
can be serialized and deserialized.
## How to Use this Add-On

Let's start by creating a record `ApiOperation` containing one ordinary and one <tt>Result</tt> field:
Add this Maven dependency to your build:

* **Group ID**: `com.leakyabstractions`
* **Artifact ID**: `result-micronaut-serde`
* **Version**: `1.0.0.0`

[Maven Central][ARTIFACTS] provides snippets for different build tools to declare this dependency.


## Test Scenario

Let's start by creating a record `ApiOperation` containing one ordinary and one Result field.

```java
{% include_relative result-micronaut-serde/src/test/java/example/ApiOperation.java %}
Expand All @@ -24,35 +37,32 @@ Let's start by creating a record `ApiOperation` containing one ordinary and one

## Problem Overview

We will take a look at what happens when we try to serialize and deserialize <tt>ApiOperation</tt> objects with
Micronaut.

First, let's make sure we're using the latest versions of both the [Micronaut Framework][MICRONAUT_LAUNCH] and the
[Result library][RESULT_LATEST].
We will take a look at what happens when we try to serialize and deserialize `ApiOperation` objects with Micronaut.


### Serialization Problem

Now, let's create a Micronaut controller that returns an instance of `ApiOperation` containing a successful result:
Now, let's create a Micronaut controller that returns an instance of `ApiOperation` containing a successful result.

```java
{% include_relative result-micronaut-serde/src/test/java/example/ApiController.java fragment="get_endpoint" %}
```

And finally, let's run the application and try the `/operations/last` endpoint we just created:
And finally, let's run the application and try the `/operations/last` endpoint we just created.

```bash
curl 'http://localhost:8080/operations/last'
```

We'll see that we get a Micronaut `CodecException` caused by a `SerdeException`:
We'll see that we get a Micronaut `CodecException` caused by a [`SerdeException`][SERDE_EXCEPTION].

```
{% include_relative result-micronaut-serde/src/test/resources/serialization_error.txt %}
```

Although this may look strange, it's actually what we should expect. Even though we annotated `ApiOperation` as
`@Serdeable`, Micronaut doesn't know how to serialize result objects yet, so the data structure cannot be serialized.
[`@Serdeable`][SERDEABLE], Micronaut doesn't know how to serialize result objects yet, so the data structure cannot be
serialized.

```java
{% include_relative result-micronaut-serde/src/test/java/example/ProblemTest.java test="serialization_problem" %}
Expand All @@ -67,100 +77,101 @@ This is Micronaut's default serialization behavior. But we'd like to serialize t

### Deserialization Problem

Now, let's reverse our previous example, this time trying to receive an <tt>ApiOperation</tt> as the body of a `POST`
request:
Now, let's reverse our previous example, this time trying to receive an `ApiOperation` as the body of a `POST` request.

```java
{% include_relative result-micronaut-serde/src/test/java/example/ApiController.java fragment="post_endpoint" %}
```

We'll see that now we get an `InvalidDefinitionException`. Let's view the stack trace:
We'll see that now we get an [`IntrospectionException`][INTROSPECTION_EXCEPTION]. Let's inspect the stack trace.

```
{% include_relative result-micronaut-serde/src/test/resources/deserialization_error.txt %}
```

This behavior again makes sense. Essentially, Micronaut doesn't have a clue how to create new <tt>Result</tt> objects,
because <tt>Result</tt> is not annotated as `@Introspected` or `@Serdeable`.
This behavior again makes sense. Essentially, Micronaut cannot create new result objects, because `Result` is not
annotated as [`@Introspected`][INTROSPECTED] or [`@Serdeable`][SERDEABLE].

```java
{% include_relative result-micronaut-serde/src/test/java/example/ProblemTest.java test="deserialization_problem" %}
```


## Solution
## Solution Implementation

What we want, is for Micronaut to treat <tt>Result</tt> values as JSON objects that contain either a `success` or a
`failure` value. Fortunately, this problem has been solved for us. [This library][RESULT_MICRONAUT_SERDE_REPO] provides
serialization / deserialization support for <tt>Result</tt> objects.
What we want, is for Micronaut to treat Result values as JSON objects that contain either a `success` or a `failure`
value. Fortunately, there's an easy way to solve this problem.

All we need to do now is add the latest version as a Maven dependency:

- groupId: `com.leakyabstractions`
- artifactId: `result-micronaut-serde`
- version: `{{ site.current_version }}`
### Adding the Serde Imports to the Classpath

Once the library is in the classpath, all functionality is available for all normal Micronaut operations.
All we need to do now is add Result-Micronaut-Serde as a Maven dependency. Once the library is in the classpath, all
functionality is available for all normal Micronaut operations.


### Serialization Solution
## Serializing Results

Now, let's try and serialize our `ApiOperation` object again:
Now, let's try and serialize our `ApiOperation` object again.

```java
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="serialization_solution_successful_result" %}
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="serialize_successful_result" %}
```

If we look at the serialized response, we'll see that this time the `result` field contains a `success` field:
If we look at the serialized response, we'll see that this time the `result` field contains a `success` field.

```json
{% include_relative result-micronaut-serde/src/test/resources/serialization_solution_successful_result.json %}
```

Next, we can try serializing a failed result:
Next, we can try serializing a failed result.

```java
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="serialization_solution_failed_result" %}
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="serialize_failed_result" %}
```

And we can verify that the serialized response contains a non-null `failure` value and a null `success` value:
We can verify that the serialized response contains a non-null `failure` value and a null `success` value:

```json
{% include_relative result-micronaut-serde/src/test/resources/serialization_solution_failed_result.json %}
```


### Deserialization Solution
## Deserializing Results

Now, let's repeat our tests for deserialization. If we read our `ApiOperation` again, we'll see that we no longer get an
`InvalidDefinitionException`:
`InvalidDefinitionException`.

```java
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="deserialization_solution_successful_result" %}
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="deserialize_successful_result" %}
```

Finally, let's repeat the test again, this time with a failed result. We'll see that yet again we don't get an
exception, and in fact, have a failed <tt>Result</tt>:
exception, and in fact, have a failed result.

```java
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="deserialization_solution_failed_result" %}
{% include_relative result-micronaut-serde/src/test/java/example/SolutionTest.java test="deserialize_failed_result" %}
```


## Conclusion

We've shown how to use [Result][RESULT] with [Micronaut][MICRONAUT] without any problems by leveraging the
[Micronaut serialization support for Results][RESULT_MICRONAUT_SERDE_REPO],
demonstrating how it enables Micronaut to treat <tt>Result</tt> objects as ordinary fields.
You have learned how to use results with [Micronaut][MICRONAUT] without any problems by leveraging the
[Micronaut serialization support for Result][RESULT_MICRONAUT_SERDE_REPO], demonstrating how it enables Micronaut to
treat Result objects as ordinary fields.

The implementation of these examples can be found [here][EXAMPLE].
The full source code for the examples is [available on GitHub][SOURCE_CODE].


[EXAMPLE]: https://github.com/LeakyAbstractions/result-micronaut-serde/blob/main/result-micronaut-serde/src/test/java/example/SolutionTest.java
[MICRONAUT_LAUNCH]: https://micronaut.io/launch
[ARTIFACTS]: https://central.sonatype.com/artifact/com.leakyabstractions/result-jackson/
[INTROSPECTED]: https://javadoc.io/doc/io.micronaut/micronaut-core/latest/io/micronaut/core/annotation/Introspected.html
[INTROSPECTION_EXCEPTION]: https://javadoc.io/doc/io.micronaut/micronaut-core/latest/io/micronaut/core/beans/exceptions/IntrospectionException.html
[MICRONAUT]: https://micronaut.io/
[MICRONAUT_SERDE]: https://micronaut-projects.github.io/micronaut-serialization/latest/guide/
[RESULT]: https://dev.leakyabstractions.com/result/
[RESULT_MICRONAUT_SERDE_REPO]: https://github.com/LeakyAbstractions/result-micronaut-serde/
[RESULT]: https://result.leakyabstractions.com/
[RESULT_LATEST]: https://search.maven.org/artifact/com.leakyabstractions/result/
[RESULT_MICRONAUT_SERDE_REPO]: https://github.com/LeakyAbstractions/result-micronaut-serde/
[RESULT_REPO]: https://github.com/LeakyAbstractions/result/
[SERDEABLE]: https://javadoc.io/doc/io.micronaut.serde/micronaut-serde-api/latest/io/micronaut/serde/annotation/Serdeable.html
[SERDE_EXCEPTION]: https://javadoc.io/doc/io.micronaut.serde/micronaut-serde-api/latest/io/micronaut/serde/exceptions/SerdeException.html
[SOURCE_CODE]: https://github.com/LeakyAbstractions/result-micronaut-serde/tree/main/result-micronaut-serde/src/test/java/example
[SPRING_BOOT]: https://spring.io/projects/spring-boot
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/**
* Creates arguments of {@link Result} type.
*
* @author Guillermo Calvo
* @author <a href="https://guillermo.dev/">Guillermo Calvo</a>
*/
public class ResultArgument {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
/**
* Builds {@link Result} objects.
*
* @author Guillermo Calvo
* @author <a href="https://guillermo.dev/">Guillermo Calvo</a>
* @param <S> the type of the success value
* @param <F> the type of the failure value
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
/**
* Deserializes {@link Result} objects.
*
* @author Guillermo Calvo
* @author <a href="https://guillermo.dev/">Guillermo Calvo</a>
*/
@SerdeImport(value = Result.class, mixin = ResultDeserializer.ResultMixin.class)
@Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
/**
* Micronaut Serialization for Result
* Provides Micronaut Serialization for {@link com.leakyabstractions.result.api.Result} objects.
* <p>
* <img src="https://dev.leakyabstractions.com/result-api/result.svg" alt="Result Library">
* <h2>Micronaut Serialization for Result</h2>
* <p>
* Serialize and deserialize {@link com.leakyabstractions.result.api.Result} objects with
* <a href="https://micronaut-projects.github.io/micronaut-serialization/latest/guide/">Micronaut</a>.
*
* @author Guillermo Calvo
* @see com.leakyabstractions.result
* @author <a href="https://guillermo.dev/">Guillermo Calvo</a>
* @see <a href="https://result.leakyabstractions.com/add-ons/micronaut">Quick guide</a>
* @see <a href="https://github.com/LeakyAbstractions/result-micronaut-serde/">Source code</a>
* @see com.leakyabstractions.result.micronaut.serde.ResultArgument
* @see com.leakyabstractions.result.micronaut.serde.ResultBuilder
* @see com.leakyabstractions.result.api.Result
* @see com.leakyabstractions.result.api
*/

package com.leakyabstractions.result.micronaut.serde;
8 changes: 4 additions & 4 deletions result-micronaut-serde/src/test/java/example/ProblemTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ProblemTest {

/** {% elsif include.test == "serialization_problem" %} Test serialization problem */
@Test
void serialization_problem(ObjectMapper objectMapper) {
void testSerializationProblem(ObjectMapper objectMapper) {
// Given
ApiOperation op = new ApiOperation("setup", success("Perfect"));
// Then
Expand All @@ -37,7 +37,7 @@ void serialization_problem(ObjectMapper objectMapper) {
} // End{% endif %}{% if false %}

@Test
void serialization_error_message(ObjectMapper objectMapper) throws Exception {
void testSerializationErrorMessage(ObjectMapper objectMapper) throws Exception {
// Given
ApiOperation op = new ApiOperation("setup", success("Perfect"));
String expected;
Expand All @@ -53,7 +53,7 @@ void serialization_error_message(ObjectMapper objectMapper) throws Exception {

/** {% elsif include.test == "deserialization_problem" %} Test deserialization problem */
@Test
void deserialization_problem(ObjectMapper objectMapper) {
void testDeserializationProblem(ObjectMapper objectMapper) {
// Given
String json = """
{"name":"renew","result":{"success":"OK"}}""";
Expand All @@ -69,7 +69,7 @@ void deserialization_problem(ObjectMapper objectMapper) {
} // End{% endif %}{% if false %}

@Test
void deserialization_error_message(ObjectMapper objectMapper) throws Exception {
void testDeserializationErrorMessage(ObjectMapper objectMapper) throws Exception {
// Given
String json = """
{"name":"renew","result":{"success":"OK"}}""";
Expand Down
16 changes: 8 additions & 8 deletions result-micronaut-serde/src/test/java/example/SolutionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
@SuppressWarnings("java:S125")
class SolutionTest {

/** {% elsif include.test == "serialization_solution_successful_result" %} Test serialization solution with a successful result */
/** {% elsif include.test == "serialize_successful_result" %} Test serialization solution with a successful result */
@Test
void serialization_solution_successful_result(ObjectMapper objectMapper)
void serializeSuccessfulResult(ObjectMapper objectMapper)
throws IOException {
// Given
ApiOperation op = new ApiOperation("clean", success("All good"));
Expand All @@ -31,9 +31,9 @@ void serialization_solution_successful_result(ObjectMapper objectMapper)
{"name":"clean","result":{"success":"All good"}}""", json);
} // End{% endif %}{% if false %}

/** {% elsif include.test == "serialization_solution_failed_result" %} Test serialization problem with a failed result */
/** {% elsif include.test == "serialize_failed_result" %} Test serialization problem with a failed result */
@Test
void serialization_solution_failed_result(ObjectMapper objectMapper)
void serializeFailedResult(ObjectMapper objectMapper)
throws IOException {
// Given
ApiOperation op = new ApiOperation("build", failure("Oops"));
Expand All @@ -44,9 +44,9 @@ void serialization_solution_failed_result(ObjectMapper objectMapper)
{"name":"build","result":{"failure":"Oops"}}""", json);
} // End{% endif %}{% if false %}

/** {% elsif include.test == "deserialization_solution_successful_result" %} Test deserialization solution with a successful result */
/** {% elsif include.test == "deserialize_successful_result" %} Test deserialization solution with a successful result */
@Test
void deserialization_solution_successful_result(ObjectMapper objectMapper)
void deserializeSuccessfulResult(ObjectMapper objectMapper)
throws IOException {
// Given
String json = """
Expand All @@ -58,9 +58,9 @@ void deserialization_solution_successful_result(ObjectMapper objectMapper)
assertEquals("Yay", response.result().orElse(null));
} // End{% endif %}{% if false %}

/** {% elsif include.test == "deserialization_solution_failed_result" %} Test deserialization solution with a failed result */
/** {% elsif include.test == "deserialize_failed_result" %} Test deserialization solution with a failed result */
@Test
void deserialization_solution_failed_result(ObjectMapper objectMapper)
void deserializeFailedResult(ObjectMapper objectMapper)
throws IOException {
// Given
String json = """
Expand Down

0 comments on commit 5e39e6e

Please sign in to comment.