Search code examples
scalagrpcgatlingscala-gatling

Access variables in Gatling feeder with gRPC


I am trying to create my gRPC payload by reading data from a csv file(it has guestID and category as columns). I followed the example here https://github.com/phiSgr/gatling-grpc/blob/244ab372da6773102d79c65a7e4086f409d3fe94/src/test/scala/com/github/phisgr/example/GrpcExample.scala but I see a type mismatch error when I try the same. (it expects Seq[ContextKey] but here i am able to form Seq[Expression[ContextKey]])

val scn2: ScenarioBuilder = scenario("gRPC call - 50 users repeated 100 times")
  .feed(csv("testtext.csv"))
  .exec(
    grpc("gRPC request with test message")
      .rpc(RecommenderGrpc.METHOD_GET_RECOMMENDATIONS)
      .payload(RequestContext.of(Map("test" -> "test"),
        Seq(ContextKey.defaultInstance.updateExpr(
          _.id :~ $("guestID"),
          _.`type` :~ Type.GUEST
        ), ContextKey.defaultInstance.updateExpr(
          _.id :~ $("category"),
          _.`type` :~ Type.CATEGORY
        )),
        Seq())
      )
  )

(payload is a RequestContext object which takes in metadata, keys and items. metadata is a map, keys is a Seq of ContextKey and items is empty Seq. ContextKey contains string guestID or category and type).

How to use the variables in the feeder here?


Solution

  • Skip to the bottom for the solution.


    Expression[T] is an alias for Session => Validation[T]. In plain English, that is a function that constructs the payload from the session with a possibility of failure.

    You can consider an Expression[T], abstractly, "contains" a T.


    Like how a Promise in JavaScript "contains" a future value. You cannot give a Promise of T to a function that expects a T. If one wants to transform or combine Promises, that code has to be turned inside out, and supplied as an argument to .then.1

    aPromise + 1 // wrong
    aPromise.then(a => a + 1)
    

    This is the same reason why your code sample does not compile.


    Users of Gatling are not necessarily familiar with Scala, or functional programming in general. It will be counterproductive to make them understand this "wrapping" stuff.2 So there are code that help you combine the Expressions.

    For HTTP and other untyped stuff, the EL string is parsed and transformed in to an Expression under the hood.

    Protobuf messages are strongly typed, the payload cannot be easily constructed using string interpolation. So the :~ operators on lenses are used to handle the plumbing so that you do not have to manually handle the Expression wrapping.


    But now you have a function, RequestContext.of, that constructs the payload. The lens magic cannot help if you need that function. You have to write the Expression lambda yourself.3

    .payload { session =>
      for {
        guestId <- session("guestId").validate[String]
        category <- session("category").validate[String]
      } yield RequestContext.of(
        Map("test" -> "test"),
        Seq(
          ContextKey(id = guestID, `type` = Type.GUEST),
          ContextKey(id = category, `type` = Type.CATEGORY)
        ),
        Seq()
      )
    }
    

    1. Needless to say this is very cumbersome, and people now use async-await with Promises.

    2. An Expression is just the Either monad and the Reader monad stacked together, what's the problem?

    3. I may be able to write a version with lenses if I know what RequestContext.of does.