I think @ g.krastev's answer is great for your use case, and you should accept this. I just diverge a little on it to show how you can make the last step a little better with cats .
First, the template:
import java.util.UUID final case class DataA(i: Int) final case class DataB(i: Int) final case class DataC(i: Int) type Data = Int def convertA(a: DataA): Data = ai def convertB(b: DataB): Data = bi def convertC(c: DataC): Data = ci def maybeMyDataInEndpoint1(id: UUID): DataA = DataA(1) def maybeMyDataInEndpoint2(id: UUID): DataB = DataB(2) def maybeMyDataInEndpoint3(id: UUID): DataC = DataC(3)
This is basically what you have in such a way that you can copy / paste into the REPL and compile.
Now, let's first declare a way to turn each of your endpoints into something safe and unified:
def makeSafe[A, B](evaluate: UUID β A, f: A β B): UUID β Option[B] = id β Option(evaluate(id)).map(f)
In doing so, you can, for example, call the following to turn maybeMyDataInEndpoint1 into UUID => Option[A] :
makeSafe(maybeMyDataInEndpoint1, convertA)
The idea now is to turn your endpoints into a UUID => Option[A] list and add this list. Here is your list:
val endpoints = List( makeSafe(maybeMyDataInEndpoint1, convertA), makeSafe(maybeMyDataInEndpoint2, convertB), makeSafe(maybeMyDataInEndpoint3, convertC) )
Now you can reset it manually, as @ g.krastev did:
def mainCall(id: UUID): Option[Data] = endpoints.foldLeft(None: Option[Data])(_ orElse _(id))
If you're fine with the cats dependency, the notion of folding over a list of options is just a concrete example of using a common template ( Foldable and Monoid ):
import cats._ import cats.implicits._ def mainCall(id: UUID): Option[Data] = endpoints.foldMap(_(id))
There are other ways to do this even better, but in this context they may be redundant - I would probably declare a type class to turn any type into Data , for example to give makeSafe a cleaner signature type.