adam bien's blog

JAX-RS resource: one or two classes as boundary? 📎

A JAX-RS resource with POJO-style parameters:

public class Addition {

    public int first;
    public int second;
}

package com.airhacks.calculations.boundary;    

@Stateless
@Path("calculations")
public class CalculationsResource {

    @POST
    @Path("addition")
    public long add(Addition addition) {
        return addition.first + addition.second;
    }
}

is easy to unit-test:

public class CalculationsResourceTest {

    private CalculationsResource cut;

    @BeforeEach
    public void init() {
        this.cut = new CalculationsResource();
    }

    @Test
    public void testAdd() {
        long result = this.cut.add(new Addition(41, 1));
        assertThat(result, is(42l));

    }
}    
    

...and therefore does not require any splitting into JAX-RS specific and "pure" business logic ingredients.

A JAX-RS resource with "web"-dependent parameters and result values:

@Stateless
@Path("calculations")
public class CalculationsResource {

    @POST
    @Path("addition")
    public Response add(JsonObject parameters) {
        long first = parameters.getJsonNumber("first").longValue();
        long second = parameters.getJsonNumber("second").longValue();
        long result = first + second;
        return Response.
        ok(result).
        header("calculatedAt", LocalDateTime.now().toString()).
        build();
    }
}
    
requires additional treatment of method parameters and return values, as well as, inclusion of test-scoped dependencies:

public class CalculationsResourceTest {

    private CalculationsResource cut;

    @BeforeEach
    public void init() {
        this.cut = new CalculationsResource();
    }

    @Test
    public void testAdd() {
        JsonObject input = Json.createObjectBuilder().add("first", 1).add("second", 41).build();
        Response response = this.cut.add(input);
        assertThat(response.getStatus(), is(200));
        //the line below usually breaks in unit test
        long result = response.readEntity(Long.class);
        assertThat(result, is(42));
    }        
}

Splitting such a JAX-RS class into a "pure" Facade:

@Stateless
public class Calculator {
    public long add(long a, long b) {
        return a + b;
    }
}

and JAX-RS specific resource:


@Stateless
@Path("calculations")
public class CalculationsResource {

    @Inject
    Calculator calculator;

    @POST
    @Path("addition")
    public Response add(JsonObject parameters) {
        long first = parameters.getJsonNumber("first").longValue();
        long second = parameters.getJsonNumber("second").longValue();
        long result = this.calculator.add(first, second);
        return Response.ok(result).header("calculatedAt", LocalDateTime.now().toString()).build();
    }
}    

reduces the complexity of business logic and makes unit tests trivial:

public class CalculatorTest {

    private Calculator cut;
    
    @BeforeEach
    public void init() {
        this.cut = new Calculator();
    }
    
  @Test
    public void testAdd() {
        long a = 41;
        long b = 1;
        long actual = this.cut.add(a, b);
        assertThat(actual, is(42l));
    }
}        

In most cases the the boundary from BCE comprises at least two classes: the JAX-RS resource and the corresponding facade. JAX-RS resources with plain POJO parameters can be realized with single class.

Also checkout How to Structure Jakarta EE Applications for Productivity Without Bloat

See you at Web, MicroProfile and Java EE Workshops at MUC Airport, particularly at the Java EE CI/CD, Testing and Quality workshop