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

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Common Performance Management Mistakes
  • What Are Microservices and the Event Aggregator Pattern?
  • Achieving Micro-frontend Architecture Using Angular Elements
  • Micro-Frontends in a Microservice Architecture

Trending

  • Streamlining Event Data in Event-Driven Ansible
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Emerging Data Architectures: The Future of Data Management
  • Rust and WebAssembly: Unlocking High-Performance Web Apps
  1. DZone
  2. Data Engineering
  3. Databases
  4. Chopping the Monolith: The Demo

Chopping the Monolith: The Demo

Here, go beyond the theory of "chopping the monolith," and learn how to concretely achieve isolating some parts of code in a dedicated Function-as-a-Service.

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Updated Jun. 02, 22 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.2K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous blog post entitled "Chopping the Monolith," I explained my stance on microservices and why they shouldn't be your golden standard. However, I admitted that some parts of the codebase were less stable than others and had to change more frequently. I proposed "chopping" these few parts to cope with this requirement while keeping the monolith intact. As Linus Torvalds once wrote:

"Talk is cheap, show me the code!"

I want to show how to do it within the scope of a small demo project to comply with the above statement.

The Use-Case: Pricing

In my career, I've spent some years in the e-commerce domain. E-commerce in the real world is much more complex than people might think. Yet, I found that simplifications of some parts of e-commerce are easy to understand because it "speaks" to the audience.

A huge chunk of e-commerce is dedicated to pricing. Pricing rules are very volatile and need to change quite frequently. Here are some reasons:

  • Too much stock of a specific product
  • End of season: The new collection has arrived, and we need to make room in the shop (or the warehouse).
  • Studies show that decreasing the price (and thus the margin) of a product will increase sales of this product so that the company will earn more money overall.
  • Marketing purposes: For example, a product prominently branded with the company logo

Here, we have an e-commerce shop:

E-commerce shop example

We can add items to the cart and check its content:

Shopping cart items

The Initial Situation

The following diagram models the existing flow:

Existing flow diagram

The application relies on the Spring Boot framework: it's coded in Kotlin and uses the Beans and Routers DSLs. It leverages Kotlin's coroutines to implement asynchronous communication.

The code is implemented as the following:

Kotlin
 
internal fun Cart.toCheckout() = CheckoutView(this, price(this))

class CheckoutView(private val cart: Cart, val total: Double) {
    val lines: List<Pair<Product, Int>>
        get() = cart.content.entries.map { it.toPair() }
}

class CheckoutHandler(private val catalog: Catalog) {
    suspend fun fetchCheckout(req: ServerRequest) =
        ServerResponse.ok().bodyValueAndAwait(req.cart().toCheckout())
}

fun checkoutRoutes(catalog: Catalog) = coRouter {
    val handler = CheckoutHandler(catalog)
    GET("/checkout/c", handler::fetchCheckout)
}


The pricing logic is coded in its file:

Kotlin
 
fun price(cart: Cart): Double {
    return cart.content.entries                                     // 1
        .fold(0.0) { current, entry ->
            current + entry.key.price * entry.value
        }
}


Only sum up the prices of each product separately: it's a demo, after all.

At this point, pricing is strongly coupled to the CheckoutHandler.

Chopping Pricing

Before using an alternative pricing service, we have to chop the pricing service by moving it to its dedicated route. The new flow is the following:

New flow after chopping pricing service

The new architecture includes a couple of changes:

  • Pricing is exposed as a dedicated route.
  • The view doesn't return the price anymore.
  • The client orchestrates the flow between the checkout and pricing routes.

The new code reflects this:

Kotlin
 
fun price(checkout: CheckoutView): Double {
    println("Pricing computed from the monolith")
    return checkout.lines
        .fold(0.0) { current, line ->
            current + line.first.price * line.second
        }
}

class PricingHandler {
    suspend fun compute(req: ServerRequest): ServerResponse {
        val cart = req.bodyToMono<CheckoutView>().awaitSingle()
        val price = price(cart)
        return ServerResponse.ok().bodyValueAndAwait(price)
    }
}

fun pricingRoute() = coRouter {
    val handler = PricingHandler()
    POST("/price", handler::compute)
}


Opening the browser dev tools reveals both HTTP requests on the checkout page:

Status Method Domain File Initiator Type
200 GET localhost:9080 c checkout:1 json
200 POST localhost:9080 price checkout:1 plain

Using an Alternative Pricing Service

At this stage, if we decide to use an alternative pricing feature, we would have to deploy an updated version of the application with the client calling the alternative URL. Each change to the pricing alternative may require a new deployment. Since the idea is to keep the deployed monolith, we shall improve the architecture instead.

Real-world architectures rarely expose their backend services directly to the outside world. Most, if not all, organizations hide them behind a single entry-point: a Reverse Proxy.

However, Reverse Proxies are rigid regarding configuration in general and route configuration in particular. For flexibility reasons, one may be inclined to use an API Gateway. For example, the Apache APISIX API Gateway allows changing route configuration on the fly via its REST API.

Apache APISIX

I've prepared a Microsoft Azure Function where I uploaded the pricing code implemented in JavaScript:

JavaScript
 
module.exports = async function (context, req) {
    context.log('Pricing computed from the function')
    const lines = req.body.lines
    context.log(`Received cart lines: ${JSON.stringify(lines)}`)
    const price = lines.reduce(
        (current, line) => { return current + line.first.price * line.second },
        0.0
    )
    context.log(`Computed price: ${price}`)
    context.res = {
        body: price
    }
    context.done()
}


With Apache APISIX, we can configure the two routes above.

Shell
 
curl -v -i http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: xyz' -X PUT -d '
{
  "uri": "/*",                      # 1
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "chopshop:8080": 1
    }
  }
}'


Configure the generic catch-all route.

Shell
 
curl -v -i http://apisix:9080/apisix/admin/routes/2 -H 'X-API-KEY: xyz' -X PUT -d '
{
  "methods": ["POST"],
  "uris": ["/price"],                                                              # 1
  "plugins": {
    "azure-functions": {                                                           # 2
      "function_uri": "https://chopshoppricing.azurewebsites.net/api/HttpTrigger", # 3
      "authorization": {
        "apikey": "'"$AZURE_FUNCTION_KEY"'"                                        # 4
      },
      "ssl_verify": false
    }
  }
}'


  1. Configure the pricing route to use the Azure Function.
  2. Apache APISIX provides a plugin that integrates natively with Azure Functions.
  3. Function's URL
  4. Function's secret key

At this point, while the monolithic shop contains a pricing code, it's never called. We can plan to retire it during the next release.

On the other side, we can update the pricing logic according to new business requirements without deploying anything but the function itself.

Conclusion

My previous post focused on why to use microservices and, more importantly, why not to use them. The reason is to speed up the pace of deployment of some parts of the code. Instead of microservices, we can isolate these parts in a dedicated Function-as-a-Service.

In this post, I tried to go beyond the theory and show how you could achieve it concretely. It boils down to exposing the to-be-chopped part via HTTP and using an API Gateway to route the wanted requests to one's service of choice.

The complete source code for this post can be found on GitHub.

Architecture azure microservice

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Common Performance Management Mistakes
  • What Are Microservices and the Event Aggregator Pattern?
  • Achieving Micro-frontend Architecture Using Angular Elements
  • Micro-Frontends in a Microservice 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!