我正在使用Play Framework(Scala)进行微服务,并使用Kafka作为事件总线。我有一个事件使用者,它映射到一个类似于:
的事件类case class MovieEvent[T] (
mediaId: String,
config: T
)
object MovieEvent {
implicit def movieEventFormat[T: Format]: Format[MovieEvent[T]] =
((__ \ "mediaId").format[String] ~
(__ \ "config").format[T]
)(MovieEvent.apply _, unlift(MovieEvent.unapply))
}
object MovieProvider extends SerializableEnumeration {
implicit val providerReads: Reads[MovieProvider.Value] = SerializableEnumeration.jsonReader(MovieProvider)
implicit val providerWrites: Writes[MovieProvider.Value] = SerializableEnumeration.jsonWrites
val Dreamworks, Disney, Paramount = Value
}
消费者看起来像:
class MovieEventConsumer @Inject()(movieService: MovieService
) extends ConsumerRecordProcessor with LazyLogging {
override def process(record: IncomingRecord): Unit = {
val movieEventJson = Json.parse(record.valueString).validate[MovieEvent[DreamworksConfiguration]]
movieEventJson match {
case event: JsSuccess[MovieEvent[DreamworksJobOptions]] => processMovieEvent(event.get)
case er: JsError =>
logger.error("Unrecognized MovieEvent, attempting to parse as MovieUploadEvent: " + JsError.toJson(er).toString())
try {
val data = (Json.parse(record.valueString) \ "upload").as[MovieUploadEvent]
processUploadEvent(data)
} catch {
case er: Exception => logger.error("Unrecognized kafka event", er)
}
}
}
def processMovieEvent[T](event: MovieEvent[T]): Unit = {
logger.debug(s"Received movie event: ${event}")
movieService.createMovieJob(event)
}
def processUploadEvent(event: MovieUploadEvent): Unit = {
logger.debug(s"Received upload event: ${event}")
movieService.addToCollection(event)
}
}
现在,我只能验证三种不同的MovieEvent配置中的一种(Dreamwork,Disney和Paramount)。我可以换掉我通过代码验证的那个,但那不是重点。但是,我想验证三者中的任何一个,而不必另外的消费者。我尝试过一些不同的想法,但没有一个能够编译。我是Play和Kafka的新手,并想知道是否有一个很好的方法来做到这一点。
提前致谢!
答案 0 :(得分:1)
我将假设可能的配置数量是有限的,并且在编译时都是已知的(在您的示例中为3)。
一种可能性是使MovieEvent
具有通用类型T
的密封特征。这是一个最小的例子:
case class DreamWorksJobOptions(anOption: String, anotherOption: String)
case class DisneyJobOptions(anOption: String)
sealed trait MovieEvent[T] {
def mediaId: String
def config: T
}
case class DreamWorksEvent(mediaId: String, config: DreamWorksJobOptions) extends MovieEvent[DreamWorksJobOptions]
case class DisneyEvent(mediaId: String, config: DisneyJobOptions) extends MovieEvent[DisneyJobOptions]
def tryParse(jsonString: String): MovieEvent[_] = {
// ... parsing logic goes here
DreamWorksEvent("dw", DreamWorksJobOptions("some option", "another option"))
}
val parseResult = tryParse("asdfasdf")
parseResult match {
case DreamWorksEvent(mediaId, config) => println(mediaId + " : " + config.anOption + " : " + config.anotherOption)
case DisneyEvent(mediaId, config) => println(mediaId + config)
}
打印出来
dw : some option : another option
我省略了解析部分,因为我无法访问Play Json atm。但由于您有一个密封的层次结构,您可以逐个尝试每个选项。 (而且你几乎不得不这样做,因为我们不能静态地保证DreamWorksEvent
没有与DisneyEvent
相同的Json结构 - 你需要先决定先尝试哪些,然后再将JSON解析为另一个当第一个无法解析时键入)。
现在您的其他代码非常通用。要添加新的事件类型,您只需要向MovieEvent
添加另一个子类,并确保解析逻辑处理新的案例。这里的神奇之处在于,您在引用T
时不必指定MovieEvent
,因为您知道您有密封的层次结构,因此可以通过模式匹配恢复T
。