DZone
Microservices Zone
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
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Microservices Zone > 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.

Nicolas Fränkel user avatar by
Nicolas Fränkel
CORE ·
Jun. 02, 22 · Microservices Zone · Tutorial
Like (3)
Save
Tweet
6.68K 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.

Popular on DZone

  • Top ALM Tools and Solutions Providers
  • Saving Memory In Java: Making The Smallest Memory Footprint
  • No-Code/Low-Code Use Cases in the Enterprise
  • Getting Started With RSocket Kotlin

Comments

Microservices Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo