DEV Community

Farhan
Farhan

Posted on

Hello Dev.to!

Hello World Dev.to!

This is my first post on dev.to, so let's start with the usual Hello World Dev.to! app, using Java and applying Clean Architecture (as per my understanding).

Our tiny application provides one single use case, with the following spec:

  • inputs: name and country
  • outputs: greeting message according to the country
  • steps:
    1. the user submits name and country 2-digit code
    2. the system fetch associated greeting word with the provided country code
    3. the system displays message by concatenating the provided name and fetched greeting word
  • exceptions:
    • missing name: display error message
    • unknown country: use English greeting

Clean architecture (briefly)

The Clean Architecture (CA) term was coined by R.C. Martin and consists of 4 concentric layers:

  • Domain Business Logic (the innermost circle/layer)
  • Application Business Logic
  • Interface Adapters
  • Framework & Drivers (the outermost circle/layer)

The number of layers/circles can be less or more than 4, but the most important rule is that no layer/circle can have access to an outer layer/circle.

For more details on CA, you can read this series on clean architecture and this detailed article.

In this post, the two outermost layers will be merged, and we will have the following layers (renamed):

  • Domain (the Domain Business Logic)
  • Use Cases (The Application Business Logic)
  • Infrastructure (combined Interface Adapters and Framework & Drivers)

Hello Dev.to! application structure

A picture is better than thousands words, so here is our greeting application structure:

class diagramm

Domain and Use Case classes

Our domain is a just one class (of type record) to model a country.

public record Country(String code, String greeting){}
Enter fullscreen mode Exit fullscreen mode

Our use case is materialized by 5 classes (1 implementation, 2 interfaces and 2 DTO) and uses 1 gateway interface (to interact with the persistence infrastructure). In total, we need 6 classes to code the use case.
Instead of having 6 java files (one for each class), we create just two files:

  • one API file which encloses all interfaces and DTO
  • one file for the use case implementation

GreetAPI.java, the enclosing file for all use case API code

public interface GreetAPI {

  record InboundModel(String name, String country) {}

  interface Inbound {
    void execute(InboundModel request);
  }

  record OutboundModel(String greeting) {}

  interface Outbound {
    void success(OutboundModel response);
    void failure(Throwable error);
  }

  interface Gateway {
    List<Country> countries();
  }

}
Enter fullscreen mode Exit fullscreen mode

A user (or system) has to acquire a GreetAPI.Inbound and provides a GreetAPI.InboundModel instance in order to use our use case.

Once called, our use case will perform its service and give back result (or error) by calling GreetAPI.Outbound#success method or, in case of error, GreetAPI.Outbound#failure method.

The GreetAPI.Gateway provides the list of available countries from any implementing persistence back-end.

The real work is done by the GreetUseCase class which implements GreetAPI.Inbound interface.

GreetUseCase.java file

public class GreetUseCase implements GreetAPI.Inbound {
  private final GreetAPI.Outbound presenter;
  private final GreetAPI.Gateway gateway;

  public GreetUseCase(GreetAPI.Outbound presenter, GreetAPI.Gateway gateway) {
    this.presenter = presenter;
    this.gateway = gateway;
  }

  @Override
  public void execute(GreetAPI.InboundModel request) {
    try {
      if (request.name() == null || request.name().isBlank()) {
        presenter.failure(new Throwable("Name is missing!"));
      } else {
        String greeting = gateway.countries().stream()
          .filter(c -> c.code().equals(request.country()))
          .map(Country::greeting)
          .findFirst()
          .orElse("Hello");
        String msg = greeting.concat(", ")
          .concat(request.name())
          .concat("!");
        presenter.success(new GreetAPI.OutboundModel(msg));
      }
    } catch (Exception ex) {
      presenter.failure(ex);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We have now a fully functional greeting use case, with necessary interfaces to interact with any adapting infrastructure, and our first infrastructure will be test code!

Testing (or first infrastructure)

For testing purpose, we will use list based GreetAPI.Gateway implementation.

  private final GreetAPI.Gateway gateway = () ->
    Arrays.asList(
      new Country("EN", "Hello"),
      new Country("ES", "Hola"));
  //this is our presenter (see next)
  private final GreetPresenter presenter = new GreetPresenter(); 
Enter fullscreen mode Exit fullscreen mode

An implementation will be also provided for the GreetAPI.Outbound, that captures the result (response or error) and provides it through GreetPresenter#get method.

  public class GreetPresenter implements GreetAPI.Outbound {
    GreetAPI.OutboundModel _response = null;
    Throwable _error = null;

    @Override
    public void success(GreetAPI.OutboundModel response) {
      _response = response;
      _error = null;
    }

    @Override
    public void failure(Throwable error) {
      _response = null;
      _error = error;
    }

    public Optional<GreetAPI.OutboundModel> get() throws Throwable {
      if (_response != null) {//success
        return Optional.of(_response);
      }
      if (_error != null) {//failure
        throw _error;
      }
      return Optional.empty();//neither!
    }
  }
Enter fullscreen mode Exit fullscreen mode

Three scenarios have been identified (as per the spec):

  • non null name with existing country (happy path)
  • null name (first exception case)
  • non null name with unknown country code (second exception case)

Happy path

  @Test
  void validNameAndKnownCountry() throws Throwable {
    GreetAPI.Inbound greet = new GreetUseCase(presenter, gateway);
    greet.execute(new GreetAPI.InboundModel("Sancho", "ES"));
    GreetAPI.OutboundModel response = presenter.get().get();
    assertThat(response.greeting()).isEqualTo("Hola, Sancho!");
  }
Enter fullscreen mode Exit fullscreen mode

Missing (null) name path

  @Test
  void missingName() {
    GreetAPI.Inbound greet = new GreetUseCase(presenter, gateway);
    greet.execute(new GreetAPI.InboundModel(null, "ES"));
    assertThatCode(presenter::get)
      .hasMessageContaining("Name is missing!");
  }
Enter fullscreen mode Exit fullscreen mode

Unknown country code path

  @Test
  void unknownCountry() throws Throwable {
    GreetAPI.Inbound greet = new GreetUseCase(presenter, gateway);
    greet.execute(new GreetAPI.InboundModel("Dupont", "FR"));
    GreetAPI.OutboundModel response = presenter.get().get();
    assertThat(response.greeting()).isEqualTo("Hello, Dupont!");
  }
Enter fullscreen mode Exit fullscreen mode

Infrastructure details

It's time to plug our use case into concrete infrastructure.
For persistence, we will use and embedded H2 database containing one table (named country) with two columns (code and greeting).

Here is our gateway implementation.

GreetGatewayJdbc.java file

public class GreetGatewayJdbc implements GreetAPI.Gateway {
  private final DataSource ds;

  public GreetGatewayJdbc(DataSource ds) {
    this.ds = ds;
  }

  @Override
  public List<Country> countries() {
    List<Country> countries = new ArrayList<>();
    String sql = "select code, greeting from country";
    try (PreparedStatement pstm = ds.getConnection().prepareStatement(sql)) {
      ResultSet rs = pstm.executeQuery();
      while (rs.next()) {
        countries.add(new Country(rs.getString("code"), rs.getString("greeting")));
      }
      return countries;
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

For user interface, we will use HTML, one page as form to collect name and country, another page to display the greeting. Instead of file on disk, string HTML will be used.
Two end point will be available:

  • GET /greet (the form page)
  • POST /greet (the greeting page)

The JDK Http Server (which is included with all jdk since version 6) is used as web server.

Here is our web based presenter. On success response, it renders a greeting HTML page and on failure, it renders an error HTML page with exception message. It uses the HttpExchange object provided by the Http Server.

GreetHtmlPresenter.java file

public class GreetHtmlPresenter implements GreetAPI.Outbound {
  private final HttpExchange exchange;

  public GreetHtmlPresenter(HttpExchange exchange) {
    this.exchange = exchange;
  }

  @Override
  public void success(GreetAPI.OutboundModel response) {
    String html = """
      <html>
        <head><title>Greeting!</title></head>
        <body><h1>%s</h1></body>
      </html>
      """.formatted(response.greeting());
    try {
      exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
      exchange.getResponseBody().write(html.getBytes());
      exchange.getResponseBody().close();
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

  @Override
  public void failure(Throwable error) {
    String html = """
      <html>
        <head><title>Greeting!</title></head>
        <body><h1>Internal error!</h1><h3>%s</h3></body>
      </html>
      """.formatted(error.getMessage());
    try {
      exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0);
      exchange.getResponseBody().write(html.getBytes());
      exchange.getResponseBody().close();
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The HTTP based controller is defined below. It extracts form field values submitted by the user and call the use case.

GreetHttpController.java file

public class GreetHttpController {
  private final HttpExchange exchange;
  private final GreetAPI.Inbound useCase;

  public GreetHttpController(HttpExchange exchange, GreetAPI.Inbound useCase) {
    this.exchange = exchange;
    this.useCase = useCase;
  }

  public void handle() throws IOException {
    if(exchange.getRequestMethod().equalsIgnoreCase("POST")) {
      String req = new String(exchange.getRequestBody().readAllBytes());
      String[] params = req.split("&");
      Map<String, String> values = new HashMap<>();
      values.put(params[0].split("=")[0], params[0].split("=")[1]);
      values.put(params[1].split("=")[0], params[1].split("=")[1]);
      useCase.execute(new GreetAPI.InboundModel(values.get("name"), values.get("country")));
    } else {
      exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In order to make our web application fully functional, we need to create an HttpHandler that glues together our controller and presenter. This handler will also displays the form page.

GreetHandler.java file

class GreetHandler implements HttpHandler {
    private final GreetAPI.Gateway gateway;

    private GreetHandler(GreetAPI.Gateway gateway) {
      this.gateway = gateway;
    }

    @Override
    public void handle(HttpExchange exchange) throws IOException {
      String method = exchange.getRequestMethod().toUpperCase();
      if ("POST".equals(method)) {
        //display greeting page
        GreetAPI.Outbound presenter = new GreetHtmlPresenter(exchange);
        GreetAPI.Inbound useCase = new GreetUseCase(presenter, gateway);
        new GreetHttpController(exchange, useCase).handle();
      } else {
        //display form to collect name and country
        exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
        exchange.getResponseBody().write(greetingForm.getBytes());
        exchange.getResponseBody().close();
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Greeting form html

String greetingForm = """
    <html>
      <head><title>Welcome</title></head>
      <body>
        <h2>Please provide your name and country code.</h2>
        <form action='/greet' method='POST'>
            <label for="name" style="display: inline-block; width: 70px">Name:</label>
            <input id="name" name='name'/>
            <br/><br/>
            <label for="country" style="display: inline-block; width: 70px">Country:</label>
            <input id="country" name="country"/>
            <br/><br/>
            <button type='submit'>OK</button>
        </form>
      </body>[](url)
    </html>
    """;
Enter fullscreen mode Exit fullscreen mode

The application entry point

Finally, this is our static main().

  public static void main(String[] args) throws IOException {
    JdbcDataSource jds = new JdbcDataSource();
    jds.setUrl("jdbc:h2:mem:data;db_close_delay=-1;init=runscript from 'classpath:script/init.sql'");
    HttpServer server = HttpServer.create(new InetSocketAddress(8182), 0);
    server.createContext("/greet", new GreetHandler(new GreetGatewayJdbc(jds)));
    server.start();
  }
  }
Enter fullscreen mode Exit fullscreen mode

Before configuring and starting the Http Server, a JDBC data source to an embedded H2 database is configured with an initialization sql script.

init.sql file

create table if not exists  country(
    code varchar(2) not null primary key,
    greeting varchar(64) not null
);
delete from country;
insert into country (code, greeting) values ('EN', 'Hello');
insert into country (code, greeting) values ('ES', 'Hola');
Enter fullscreen mode Exit fullscreen mode

After you run the entry point method, the application is available on http://localhost:8182/greet!

Top comments (0)