ing-bank / baker

Orchestrate microservice-based process flows
https://ing-bank.github.io/baker/
MIT License
333 stars 83 forks source link

Limitations of the InteractionInstances: Lambda interface implementation with higher kinded types #253

Open VledicFranco opened 5 years ago

VledicFranco commented 5 years ago

Implementing an interaction by creating an interface, instantiating the interface with a lambda, and then using the reflection API to create an InteractionInstance blows into a runtime exception WHEN the ingredients have a higher kinded type like List[A]. This is because the lambda instantiation of the interface erases the parametrized type. See example below:

trait ReserveItems {

  def apply(orderId: String, items: List[String]): Future[WebshopRecipeReflection.ReserveItemsOutput]
}

  val reserveItemsImplementation: ReserveItems =
    (orderId: String, items: List[String]) => {

      // Http call to the Warehouse service
      val response: Future[Either[List[String], List[String]]] =
      // This is mocked for the sake of the example
        Future.successful(Right(items))

      // Build an event instance that Baker understands
      response.map {
        case Left(unavailableItems) =>
          WebshopRecipeReflection.OrderHadUnavailableItems(unavailableItems)
        case Right(reservedItems) =>
          WebshopRecipeReflection.ItemsReserved(reservedItems)
      }
    }

  val alternativeReserveItemInstance: InteractionInstance =
    InteractionInstance.unsafeFrom(reserveItemsImplementation)
Caused by: java.lang.IllegalArgumentException: Unsupported parameter type for interaction implementation 'ReserveItems'
    at com.ing.baker.runtime.scaladsl.InteractionInstance$.$anonfun$unsafeFrom$7(InteractionInstance.scala:89)
    at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
    at scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:32)
    at scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:29)
    at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:191)
    at scala.collection.TraversableLike.map(TraversableLike.scala:234)
    at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
    at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:191)
    at com.ing.baker.runtime.scaladsl.InteractionInstance$.unsafeFrom(InteractionInstance.scala:86)
    at webshop.WebshopInstancesReflection$.<init>(WebshopInstancesReflection.scala:54)
    at webshop.WebshopInstancesReflection$.<clinit>(WebshopInstancesReflection.scala)
    ... 11 more
Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
    at com.ing.baker.types.package$.getTypeParameter(package.scala:25)
    at com.ing.baker.types.modules.ScalaModules$ListModule.readType(ScalaModules.scala:13)
    at com.ing.baker.types.modules.ScalaModules$ListModule.readType(ScalaModules.scala:10)
    at com.ing.baker.types.TypeAdapter.readType(TypeAdapter.scala:48)
    at com.ing.baker.types.Converters$.readJavaType(Converters.scala:47)
    at com.ing.baker.runtime.scaladsl.InteractionInstance$.$anonfun$unsafeFrom$7(InteractionInstance.scala:87)
    ... 21 more
VledicFranco commented 5 years ago

It was found that it is not lambda specific, this will also break it:

val reserveItemsInstance: InteractionInstance = InteractionInstance.unsafeFrom(
    new ReserveItems {

      def apply(orderId: String, items: List[String]): Future[WebshopRecipeReflection.ReserveItemsOutput] = {

        // Http call to the Warehouse service
        val response: Future[Either[List[String], List[String]]] =
        // This is mocked for the sake of the example
          Future.successful(Right(items))

        // Build an event instance that Baker understands
        response.map {
          case Left(unavailableItems) =>
            WebshopRecipeReflection.OrderHadUnavailableItems(unavailableItems)
          case Right(reservedItems) =>
            WebshopRecipeReflection.ItemsReserved(reservedItems)
        }
      }
    }
  )
VledicFranco commented 5 years ago

Also in Java interactions must be direct class instances, if they are a reference to other class members or to static attributes, the Interactionmanager can't access them

SemanticBeeng commented 5 years ago

Not sure if my issue is rooted in same cause but sounds close so posting here first.

Have interactions that use SessionId as ingredient.

case class SessionId(value: java.util.UUID) extends AnyVal

At runtime am getting "No implementation provided for interaction". Tried to trace in debugger and seems the issue is the mismatch between these

RecordType(WrappedArray(RecordField(mostSigBits,Int64), RecordField(leastSigBits,Int64)))

RecordType(WrappedArray(RecordField(value,RecordType(WrappedArray(RecordField(mostSigBits,Int64), RecordField(leastSigBits,Int64))))))

Sounds like the UUID value gets unwrapped. :thinking: Does this make any sense to you? Is my design of interaction valid (taking case classes as ingredients?

I do use complex case classes as ingredients and they do not cause this issue.


UPDATE: Solved. This was caused by SessionId being an AnyVal.