DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Applying Kappa Architecture to Make Data Available Where It Matters
  • Why Text2SQL Alone Isn’t Enough: Embracing TAG
  • Leveraging Neo4j for Effective Identity Access Management
  • Guide to LangChain Runnable Architecture

Trending

  • Proactive Security in Distributed Systems: A Developer’s Approach
  • How to Format Articles for DZone
  • Building a Real-Time Change Data Capture Pipeline With Debezium, Kafka, and PostgreSQL
  • Caching 101: Theory, Algorithms, Tools, and Best Practices
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Reactive Clean Architecture With Vert.x

Reactive Clean Architecture With Vert.x

Clean Architecture gives a clear No! to mixing frameworks and business rules. But that doesn't mean we have to resign from all cool technologies. We just have to adapt.

By 
Grzegorz Ziemoński user avatar
Grzegorz Ziemoński
·
Mar. 17, 17 · Tutorial
Likes (21)
Comment
Save
Tweet
Share
25.5K Views

Join the DZone community and get the full member experience.

Join For Free

I have recently written about Clean Architecture and provided an implementation example by Uncle Bob himself. This time, we’ll look at the implementation from a bit different angle — i.e. is it possible to implement Clean Architecture and still take advantage of modern, reactive frameworks like Vert.x? Here it comes!

What Was That Clean Architecture Again?

Clean Architecture divides our code into four conceptual circles – Enterprise Business Rules, Application Business Rules, Interface Adapters, and Frameworks & Drivers. Between these circles, there is a strong dependency rule – dependencies can only flow inwards (see the picture below). One of the rule’s implications is that the code in the inner part should have no knowledge of the frameworks being used. In our case, these will be Vert.x and Guice. In general, you should be able to switch UI technologies, databases, and frameworks without impacting the two inner circles.

If you want to read more about the theory behind Clean Architecture, make sure to check out my previous post and Uncle Bob’s upcoming book.

Reference Implementation

When working towards my reactive implementation, I used Uncle Bob’s repository as a reference:

One could go crazy with all the types of objects in the codecastSummaries use case package above. To make sure we’ll all on the same page, let me explain how I understand the role of each of them:

  • Controller – handles input e.g. web requests and orchestrates the work between the use case, view, and presenter
  • Input Boundary – an interface to the use case, a clear indication where the Interface Adapters circle ends and Use Cases circle begins
  • Output Boundary – an interface to the present the use case output, a clear indication where the use case ends processing and presentation begins
  • Use Case – orchestrates entities and other classes (e.g. external services) to satisfy a user’s goal
  • Response Model – a “stupid” representation of the use case results – we don’t want any business logic to leak through the output boundary
  • Presenter – converts data to a presentable form and presents them using a view
  • View Model – a presentable form of data. It’s usually similar to response model, with the only difference being that the response model does not care about being presentation-friendly
  • View – an object that handles actual displaying on the screen e.g. rendering an HTML page

It’s good to think about those objects as useful roles instead of obligatory parts of the architecture. Sometimes common sense suggests skipping some of them or making one object play multiple roles and we shouldn’t constrain ourselves too much.

Problem Domain

Despite using it as a reference, I didn’t want to code up the same thing as in Uncle Bob’s repository. I set out to implement something extremely simple – displaying a list of activities in which, for now, an activity consists only of a name. I wanted to put as close to 100% focus on the architecture and as close to 0% focus on the domain itself. Hence, my Activity entity ended up looking like this:

public class Activity extends Entity {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The Entity base class looks almost the same, with the difference that it contains an id field instead of name.

The Use Case

As I already mentioned, the use case is really simple. All it needs to do is retrieve the data from a database and pass it through the output boundary:

public class ListActivitiesUseCase implements ListActivitiesInputBoundary {
    private final ActivityGateway activityGateway;

    @Inject
    public ListActivitiesUseCase(ActivityGateway activityGateway) {
        this.activityGateway = activityGateway;
    }

    @Override
    public void listActivities(ListActivitiesOutputBoundary presenter) {
        activityGateway.findAll(Callback.of(
                activities -> presenter.success(toResponseModel(activities)),
                presenter::failure));
    }

    private List<ActivityDetails> toResponseModel(List<Activity> activities) {
        return activities
                .stream()
                .map(Activity::getName)
                .map(ActivityDetails::new)
                .collect(Collectors.toList());
    }
}

As the response model would consist of a single field, we’re passing a collection of ActivityDetails directly through the boundary. This could be considered short-sighted, as any new field in the response model would force us to wrap it in a class and change the output boundary, but it saves us some brain cycles analyzing the code for some time.

Don’t Call Us, We’ll Call You

Since in the reactive approach the thread responsible for processing our request should not wait for the results of a use case and catch exceptions, we need a way to process these asynchronously. The easiest way to deal with this is by using callbacks. But since we don’t want to couple our code to the framework, we need to use our own piece of code, instead of Vert.x’s Handler and  AsyncResult. That’s why I created the  Callback interface:

public interface Callback<T> {

    static <T> Callback<T> of(Consumer<T> success, Consumer<Throwable> failure) {
        return new DelegatingCallback<>(success, failure);
    }

    void success(T t);

    void failure(Throwable throwable);

    class DelegatingCallback<T> implements Callback<T> {
        private final Consumer<T> success;
        private final Consumer<Throwable> failure;

        // delegate
    }
}

As you can see, it contains two methods: success for asynchronously returning a result and failure for asynchronous indication of an exception. You can see the class in action in the use case implementation above. The use case invokes a database query, which can take a long time and thus was implemented in a non-blocking manner using callbacks:

public class ActivityGatewayImpl implements ActivityGateway {
    // fields & c-tor

    @Override
    public void findAll(Callback<List<Activity>> callback) {
        getConnection(connection -> connection.query("SELECT * FROM Activities;", asyncRs -> {
            if (asyncRs.succeeded()) {
                List<Activity> activities = asyncRs.result()
                        .getRows()
                        .stream()
                        .map(this::toActivity)
                        .collect(Collectors.toList());

                callback.success(activities);
            } else {
                callback.failure(asyncRs.cause());
            }
        }), callback::failure);
    }

    // getConnection() & toActivity()
}

The Output Boundary

The output boundary of our use case is responsible only for presenting activities and communicating a failure if necessary. Hence, I made it extend the Callback interface directly:

public interface ListActivitiesOutputBoundary extends Callback<List<ActivityDetails>> {
}

Since there’s no need for a special view model, and a presenter of the use case would only pass the result straight to the view, I decided to skip it and let the view play the role of an output boundary instead. Hence, the view was implemented like this:

public class ListActivitiesView implements ListActivitiesOutputBoundary {
    private final RoutingContext ctx;

    public ListActivitiesView(RoutingContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public void success(List<ActivityDetails> activityDetailsList) {
        FreeMarkerTemplateEngine engine = FreeMarkerTemplateEngine.create();

        ctx.put("activities", activityDetailsList);

        engine.render(ctx, "templates/index.ftl", res -> {
            if (res.succeeded()) {
                ctx.response().end(res.result());
            } else {
                ctx.fail(res.cause());
            }
        });
    }

    @Override
    public void failure(Throwable throwable) {
        ctx.fail(throwable);
    }
}

Since the view implementation is part of the UI, it can contain direct references to the Vert.x framework i.e. the RoutingContext.

Binding Things Together

Now that we have all the pieces of our use case, we can bind them together and expose them via the web. Binding is done using a simple Guice module:

public class ActivityModule extends AbstractModule {
    private final Vertx vertx;
    private final JsonObject config;

    public ActivityModule(Vertx vertx, JsonObject config) {
        this.vertx = vertx;
        this.config = config;
    }

    @Override
    protected void configure() {
        bind(JDBCClient.class)
                .toInstance(JDBCClient.createShared(vertx, config));
        bind(ActivityGateway.class)
                .to(ActivityGatewayImpl.class);
        bind(ListActivitiesInputBoundary.class)
                .to(ListActivitiesUseCase.class);
    }
}

And exposing via the web is done using the Vert.x Web Router mechanism:

public class MainVerticle extends AbstractVerticle {

    @Override
    public void start() {
        Router router = Router.router(vertx);
        Injector injector = Guice.createInjector(new ActivityModule(vertx, config()));

        ListActivitiesInputBoundary listActivitiesUseCase = injector.getInstance(ListActivitiesUseCase.class);
        router.get().handler(ctx -> {
            ListActivitiesView view = new ListActivitiesView(ctx);
            listActivitiesUseCase.listActivities(view);
        });

        vertx.createHttpServer()
                .requestHandler(router::accept)
                .listen(8080);
    }
}

Wrap Up

That’s it. The final code structure looks like this:

As you can see, newer frameworks like Vert.x and Clean Architecture are not mutually exclusive. What’s more, there’s not much extra coding that you need to do to prevent the framework from spreading all over your codebase.

You can find all the code from this article here.

Architecture Use case Vert.x Database Framework

Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Applying Kappa Architecture to Make Data Available Where It Matters
  • Why Text2SQL Alone Isn’t Enough: Embracing TAG
  • Leveraging Neo4j for Effective Identity Access Management
  • Guide to LangChain Runnable Architecture

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!