Firefly v4.5.0 is released

27 Oct 2017, Alvin Qiu

Firefly v4.5.0 adds brand new Kotlin DSL API, asynchronous SQL client, and reactive stream adapter that helps us easier to building the asynchronous application.

Kotlin DSL

Kotlin DSL APIs allow for constructing the application in a semi-declarative way.

For example:

private val log = Log.getLogger { }

fun main(args: Array<String>) {
    val server = HttpServer(requestLocal) {
        router {
            httpMethod = HttpMethod.GET
            path = "/"

            asyncHandler {
                end("hello world!")
            }
        }

        router {
            httpMethods = listOf(GET, POST)
            path = "/product/:type/:id"

            asyncHandler {
                statusLine {
                    status = OK.code
                    reason = OK.message
                }

                header {
                    "My-Header" to "Ohh nice"
                    SERVER to "Firefly kotlin DSL server"
                    +HttpField("Add-My-Header", "test add")
                }

                trailer {
                    "You-are-trailer" to "Crane ....."
                }

                val type = getRouterParameter("type")
                val id = getRouterParameter("id")
                log.info { "req type: $type, id: $id" }

                writeJson(Response("ok", 200, Product(id, type))).end()
            }
        }
    }
    server.listen("localhost", 8080)
}

We set HTTP method, path, response status line, headers, trailers using Kotlin DSL. It expresses HTTP protocol structure is clear. And we can add our business logic in the handler.

The HTTP server (Kotlin version) executes handler in coroutines. That means we can replace asynchronous callback style codes to the block style easily. The coroutine block codes make the programs are shorter and far simpler to understand and enjoy the scalability and performance benefits of asynchronous codes.

For example:

router {
    httpMethod = HttpMethod.GET
    path = "/project/:id"

    asyncHandler {
        val response = projectService.getProject(Request("test", getPathParameter("id").toLong()))
        writeJson(response).end()
    }
}

All the DB requests are asynchronous. The DB client (Kotlin version) uses the coroutine to replacing callback-ridden codes. It keeps the scalability and performance without complex, hard-to-maintain codes.

The Java has not native coroutine. We consider to adding the Quasar (the Fiber library for JVM) adapter in future.

Reactor adapter

In this version, we add the Reactor adapter for asynchronous API. The Reactor helps us to compose asynchronous codes fluently.

For example:

@Override
public Mono<Boolean> buy(ProductBuyRequest request) {
    if (request == null) {
        return Mono.error(new IllegalArgumentException("The product request is required"));
    }

    if (request.getUserId() == null || request.getUserId().equals(0L)) {
        return Mono.error(new IllegalArgumentException("The user id is required"));
    }

    if (CollectionUtils.isEmpty(request.getProducts())) {
        return Mono.error(new IllegalArgumentException("The products must bu not empty"));
    }

    return db.newTransaction(c -> buy(request, c));
}

private Mono<Boolean> buy(ProductBuyRequest request, ReactiveSQLConnection c) {
    return inventoryDAO.updateBatch(request.getProducts(), InventoryOperator.SUB, c)
                       .doOnSuccess(this::verifyInventory)
                       .flatMap(arr -> productDAO.list(toProductIdList(request), c))
                       .map(products -> toOrders(request, products))
                       .flatMap(orders -> orderDAO.insertBatch(orders, c))
                       .map(orderIdList -> true);
}

More examples, please refer to the:

Update log:

  1. Add Kotlin DSL API.
  2. Add asynchronous SQL client.
  3. Add reactive stream adapter.
  4. Add Kotlin coroutine dispatcher for HTTP server.
  5. Add the asynchronous handler for HTTP server.
  6. Add gzip content decoding for HTTP client.
  7. Add the default path is “/” when the HTTP client path is empty.
  8. Add generic type bind for JSON parser.
  9. Add sl4fj MDC implementation.
  10. Add log message formatter.
  11. Add AffinityThreadPool.