Upgrade to Pro — share decks privately, control downloads, hide ads and more …

マイクロサービスでイベントソーシングを利用したくなる理由とその動作原理を学ぼう / Why Event Sourcing

nrs
April 22, 2022

マイクロサービスでイベントソーシングを利用したくなる理由とその動作原理を学ぼう / Why Event Sourcing

イベントソーシングがなぜ必要になるのかの解説と、その動作原理を解説する際に利用した資料です。
この資料を使ったトークが YouTube にアップロードされています。
トークURL: https://youtu.be/Jtcp9ry8ZcE

◆ URL
トークURL: https://youtu.be/Jtcp9ry8ZcE
チャンネル登録: https://www.youtube.com/c/narusemi?sub_confirmation=1
Twitter: https://twitter.com/nrslib
SampleCode: https://github.com/nrslib/microservice-with-event-sourcing-sample-kotlin

nrs

April 22, 2022
Tweet

More Decks by nrs

Other Decks in Programming

Transcript

  1. @Transactional
    public void order(String itemId) {
    var itemDao = new ItemDao();
    var item = itemDao.findById(itemId);
    ...
    var accountDao = new AccountDao();
    var account = accountDao.find(Session.Id);
    var paymentDao = new PaymentDao();
    var paymentInfo = paymentDao.find(account.id);
    paymentInfo.validate();
    creditService.approval(paymentInfo.cardInfo, order.total);
    ...
    orderDao.save(order);
    }

    View full-size slide

  2. @Transactional
    public void order(String itemId) {
    var itemDao = new ItemDao();
    var item = itemDao.findById(itemId);
    ...
    var accountDao = new AccountDao();
    var account = accountDao.find(Session.Id);
    var paymentDao = new PaymentDao();
    var paymentInfo = paymentDao.find(account.id);
    paymentInfo.validate();
    creditService.approval(paymentInfo.cardInfo, order.total);
    ...
    orderDao.save(order);
    }

    View full-size slide

  3. @Transactional
    public void order(String itemId) {
    var itemDao = new ItemDao();
    var item = itemDao.findById(itemId);
    ...
    var accountDao = new AccountDao();
    var account = accountDao.find(Session.Id);
    var paymentDao = new PaymentDao();
    var paymentInfo = paymentDao.find(account.id);
    paymentInfo.validate();
    creditService.approval(paymentInfo.cardInfo, order.total);
    ...
    orderDao.save(order);
    }

    View full-size slide

  4. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    ...
    }

    View full-size slide

  5. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  6. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  7. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  8. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  9. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  10. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  11. class OrderApplicationService(
    private val itemRepository: ItemRepository,
    private val orderRepository: AggregateRepository
    ) {
    @Transactional
    fun createOrder(accountId: String, itemAndQuantities: List): String {
    val items = itemRepository.find(itemAndQuantities.map { ItemId(it.itemId) })
    val orderItems = makeOrderItems(itemAndQuantities, items)
    val command = CreateOrderCommand(UUID.randomUUID().toString(), accountId, orderItems)
    val result = orderRepository.save(command)
    return result.idAndVersion.id
    }
    ...
    private fun makeOrderItems(itemAndQuantities: List, items: List): OrderItems {
    val orderItems = itemAndQuantities.map {
    val item = items.first { item -> item.id.value == it.itemId }
    OrderItem(
    it.quantity,
    item.price
    )
    }
    return OrderItems(orderItems)
    }
    }

    View full-size slide

  12. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  13. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  14. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  15. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  16. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  17. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    }

    View full-size slide

  18. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    fun process(command: CreateOrderCommand): List {
    val total = command.orderItems.total()
    val event = OrderCreatedEvent(command.id, command.accountId, total, command.orderItems)
    return listOf(event)
    }
    ...
    }

    View full-size slide

  19. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    ...
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    ...
    }

    View full-size slide

  20. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  21. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  22. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  23. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  24. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  25. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    fun save(command: CT): EntityWithIdAndVersion {
    val aggregate = clazz.getDeclaredConstructor().newInstance()
    val events = aggregate.processCommand(command)
    Aggregate.applyEvents(aggregate, events)
    val serializedEvents = events.map {
    EventTypeAndData(it.javaClass.name, Gson().toJson(it))
    }
    val entityIdVersionAndEventIds = aggregateStore.save(clazz, serializedEvents)
    return EntityWithIdAndVersion(
    entityIdVersionAndEventIds,
    aggregate
    )
    }
    ...
    }

    View full-size slide

  26. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    ...
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    fun apply(event: OrderApprovedEvent) {
    this.orderState = OrderState.APPROVED
    }
    fun apply(event: OrderRejectedEvent) {
    this.orderState = OrderState.APPROVAL_REJECTED
    }
    }

    View full-size slide

  27. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    ...
    fun apply(event: OrderCreatedEvent) {
    accountId = event.accountId
    total = event.total
    items = event.orderItems
    orderState = OrderState.APPROVAL_PENDING
    }
    fun apply(event: OrderApprovedEvent) {
    this.orderState = OrderState.APPROVED
    }
    fun apply(event: OrderRejectedEvent) {
    this.orderState = OrderState.APPROVAL_REJECTED
    }
    }

    View full-size slide

  28. class Order : CommandProcessingAggregate() {
    lateinit var accountId: String
    var total: Money = Money.zero()
    lateinit var items: OrderItems
    lateinit var orderState: OrderState
    ...
    fun process(command: ApproveOrderCommand): List {
    return listOf(OrderApprovedEvent())
    }
    }

    View full-size slide

  29. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  30. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  31. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  32. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  33. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  34. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  35. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  36. enum class DocumentType {
    Entity,
    Event,
    Snapshot
    }
    class EventSourcingDocument {
    @Id
    lateinit var id: String
    lateinit var type: DocumentType
    lateinit var entityId: String
    lateinit var body: String
    var version: Long = -1
    @JsonProperty("_etag")
    var etag: String = ""
    }

    View full-size slide

  37. enum class DocumentType {
    Entity,
    Event,
    Snapshot
    }
    class EventSourcingDocument {
    @Id
    lateinit var id: String
    lateinit var type: DocumentType
    lateinit var entityId: String
    lateinit var body: String
    var version: Long = -1
    @JsonProperty("_etag")
    var etag: String = ""
    }

    View full-size slide

  38. enum class DocumentType {
    Entity,
    Event,
    Snapshot
    }
    class EventSourcingDocument {
    @Id
    lateinit var id: String
    lateinit var type: DocumentType
    lateinit var entityId: String
    lateinit var body: String
    var version: Long = -1
    @JsonProperty("_etag")
    var etag: String = ""
    }

    View full-size slide

  39. class EventDataModel {
    lateinit var id: String
    lateinit var eventType: String
    lateinit var entityType: String
    lateinit var entityId: String
    lateinit var payload: String
    var version: Long = -1
    ...
    }

    View full-size slide

  40. class EntityDataModel {
    lateinit var id: String
    lateinit var type: String
    var version: Long = -1
    ...
    }

    View full-size slide

  41. class SnapshotDataModel {
    lateinit var id: String
    lateinit var entityType: String
    lateinit var entityId: String
    var entityVersion: Long = -1
    lateinit var snapshotType: String
    lateinit var snapshotData: String
    ...
    }

    View full-size slide

  42. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  43. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  44. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  45. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  46. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  47. @Beta(value = Beta.SinceVersion.V4_7_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING)
    @Deprecated() //forRemoval = true, since = "4.19"
    public final class TransactionalBatch {

    View full-size slide

  48. @Beta(value = Beta.SinceVersion.V4_7_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING)
    @Deprecated() //forRemoval = true, since = "4.19"
    public final class TransactionalBatch {

    View full-size slide

  49. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  50. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  51. class CosmosAggregateStore(...) : AggregateStore {
    override fun > save(clazz: Class, events: List, options: AggregateCrudSaveOptions?
    val container = getContainer()
    val entityId = UUID.randomUUID().toString()
    val version = events.count().toLong()
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val entityDataModel = EntityDataModel(entityId, clazz.name, version)
    val entityDocument = entityDataModel.toDocument()
    batch.createItemOperation(entityDocument)
    events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.eventType,
    clazz.name,
    entityId,
    index.toLong() + 1,
    it.eventData
    )
    }.map(EventDataModel::toDocument)
    .forEach { batch.createItemOperation(it) }
    val response = container.executeTransactionalBatch(batch)
    if (!response.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entityId, version)
    }

    View full-size slide

  52. }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels
    .map(EventDataModel::toDocument)
    .forEach {
    batch.createItemOperation(it)
    }
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit
    if (options?.snapshot != null) {
    val snapshot = options.snapshot
    val snapshotDataModel = SnapshotDataModel(
    UUID.randomUUID().toString(),
    clazz.name,
    entityIdAndVersion.id,
    latestVersion,
    snapshot!!.javaClass.name,

    View full-size slide

  53. override fun > update(clazz: Class, entityIdAndVersion: EntityIdAndVersion, events: List, option
    val entityId = entityIdAndVersion.id
    val currentVersion = entityIdAndVersion.version
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val container = getContainer()
    val entityDocumentReadResponse = container.readItem(entityId, PartitionKey(entityId), EventSourcingDocument::class.java
    val entityDocument = entityDocumentReadResponse.item
    if (currentVersion != entityDocument.version) {
    throw OptimisticConcurrencyException(entityId, currentVersion, entityDocument.version)
    }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels.map(EventDataModel::toDocument).forEach {batch.createItemOperation(it)}
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit

    View full-size slide

  54. override fun > update(clazz: Class, entityIdAndVersion: EntityIdAndVersion, events: List, option
    val entityId = entityIdAndVersion.id
    val currentVersion = entityIdAndVersion.version
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val container = getContainer()
    val entityDocumentReadResponse = container.readItem(entityId, PartitionKey(entityId), EventSourcingDocument::class.java
    val entityDocument = entityDocumentReadResponse.item
    if (currentVersion != entityDocument.version) {
    throw OptimisticConcurrencyException(entityId, currentVersion, entityDocument.version)
    }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels.map(EventDataModel::toDocument).forEach {batch.createItemOperation(it)}
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit

    View full-size slide

  55. override fun > update(clazz: Class, entityIdAndVersion: EntityIdAndVersion, events: List, option
    val entityId = entityIdAndVersion.id
    val currentVersion = entityIdAndVersion.version
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val container = getContainer()
    val entityDocumentReadResponse = container.readItem(entityId, PartitionKey(entityId), EventSourcingDocument::class.java
    val entityDocument = entityDocumentReadResponse.item
    if (currentVersion != entityDocument.version) {
    throw OptimisticConcurrencyException(entityId, currentVersion, entityDocument.version)
    }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels.map(EventDataModel::toDocument).forEach {batch.createItemOperation(it)}
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit

    View full-size slide

  56. override fun > update(clazz: Class, entityIdAndVersion: EntityIdAndVersion, events: List, option
    val entityId = entityIdAndVersion.id
    val currentVersion = entityIdAndVersion.version
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val container = getContainer()
    val entityDocumentReadResponse = container.readItem(entityId, PartitionKey(entityId), EventSourcingDocument::class.java
    val entityDocument = entityDocumentReadResponse.item
    if (currentVersion != entityDocument.version) {
    throw OptimisticConcurrencyException(entityId, currentVersion, entityDocument.version)
    }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels.map(EventDataModel::toDocument).forEach {batch.createItemOperation(it)}
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit

    View full-size slide

  57. override fun > update(clazz: Class, entityIdAndVersion: EntityIdAndVersion, events: List, option
    val entityId = entityIdAndVersion.id
    val currentVersion = entityIdAndVersion.version
    val batch = TransactionalBatch.createTransactionalBatch(PartitionKey(entityId))
    val container = getContainer()
    val entityDocumentReadResponse = container.readItem(entityId, PartitionKey(entityId), EventSourcingDocument::class.java
    val entityDocument = entityDocumentReadResponse.item
    if (currentVersion != entityDocument.version) {
    throw OptimisticConcurrencyException(entityId, currentVersion, entityDocument.version)
    }
    val eventDatamodels = events.mapIndexed { index, it ->
    EventDataModel(
    UUID.randomUUID().toString(),
    it.javaClass.name,
    clazz.name,
    entityId,
    currentVersion + index + 1,
    Gson().toJson(it)
    )
    }
    eventDatamodels.map(EventDataModel::toDocument).forEach {batch.createItemOperation(it)}
    val latestVersion = eventDatamodels.last().version
    val entity = EntityDataModel.from(entityDocument)
    entity.version = latestVersion
    val replaceEntityDocument = entity.toDocument()
    batch.replaceItemOperation(entityId, replaceEntityDocument, TransactionalBatchItemRequestOptions().setIfMatchETag(entit

    View full-size slide

  58. if (options?.snapshot != null) {
    val snapshot = options.snapshot
    val snapshotDataModel = SnapshotDataModel(
    UUID.randomUUID().toString(),
    clazz.name,
    entityIdAndVersion.id,
    latestVersion,
    snapshot!!.javaClass.name,
    Gson().toJson(snapshot)
    )
    val snapshotDocument = snapshotDataModel.toDocument()
    batch.createItemOperation(snapshotDocument)
    }
    val result = container.executeTransactionalBatch(batch)
    if (!result.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entity.id, latestVersion)
    }

    View full-size slide

  59. if (options?.snapshot != null) {
    val snapshot = options.snapshot
    val snapshotDataModel = SnapshotDataModel(
    UUID.randomUUID().toString(),
    clazz.name,
    entityIdAndVersion.id,
    latestVersion,
    snapshot!!.javaClass.name,
    Gson().toJson(snapshot)
    )
    val snapshotDocument = snapshotDataModel.toDocument()
    batch.createItemOperation(snapshotDocument)
    }
    val result = container.executeTransactionalBatch(batch)
    if (!result.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entity.id, latestVersion)
    }

    View full-size slide

  60. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  61. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  62. class AggregateRepository(
    private val clazz: Class,
    private val aggregateStore: AggregateStore
    ) where T : CommandProcessingAggregate, CT : Command {
    ...
    fun update(id: String, cmd: CT, updateOption: UpdateOptions = UpdateOptions())
    : EntityWithIdAndVersion {
    val loadedData = aggregateStore.find(clazz, id)
    ?: throw IllegalArgumentException("$id is notfound")
    val aggregate = loadedData.entity
    val events = aggregate.processCommand(cmd)
    Aggregate.applyEvents(aggregate, events)
    val idAndVersion = aggregateStore.update(
    clazz,
    loadedData.idAndVersion,
    events,
    withPossibleSnapshot(
    updateOption, aggregate, loadedData.snapshotVersion, loadedData.events, events)
    )
    return EntityWithIdAndVersion(idAndVersion, aggregate)
    }
    }

    View full-size slide

  63. class OrderSnapshotStrategy : SnapshotStrategy {
    override fun getAggregateClass(): Class<*> = Order::class.java
    override fun possiblySnapshot(
    aggregate: Aggregate<*>,
    oldEvents: List,
    newEvents: List,
    snapshotVersion: String?
    ): Snapshot? {
    val order = aggregate as Order
    val snapshot = OrderSnapshot()
    snapshot.accountId = order.accountId
    snapshot.orderState = order.orderState
    snapshot.total = order.total
    snapshot.items = order.items
    return snapshot
    }
    override fun recreateAggregate(clasz: Class, snapshot: Snapshot): T where T : Aggregate {
    val orderSnapshot = snapshot as OrderSnapshot
    val aggregate = Order()
    aggregate.accountId = orderSnapshot.accountId
    aggregate.orderState = orderSnapshot.orderState
    aggregate.total = orderSnapshot.total
    aggregate.items = orderSnapshot.items
    return aggregate as T
    }
    }

    View full-size slide

  64. if (options?.snapshot != null) {
    val snapshot = options.snapshot
    val snapshotDataModel = SnapshotDataModel(
    UUID.randomUUID().toString(),
    clazz.name,
    entityIdAndVersion.id,
    latestVersion,
    snapshot!!.javaClass.name,
    Gson().toJson(snapshot)
    )
    val snapshotDocument = snapshotDataModel.toDocument()
    batch.createItemOperation(snapshotDocument)
    }
    val result = container.executeTransactionalBatch(batch)
    if (!result.isSuccessStatusCode) {
    throw UnsupportedOperationException()
    }
    return EntityIdAndVersion(entity.id, latestVersion)
    }

    View full-size slide

  65. val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }
    if (snapshotDataModel == null && events.isEmpty()) {
    return null
    }
    val version = if (events.isEmpty()) {
    entity.version
    } else {
    events.last().version
    }
    val idAndVersion = EntityIdAndVersion(id, version)
    val recreatedAggregate = if (snapshotDataModel != null) {

    View full-size slide

  66. override fun > find(clazz: Class, id: String): EntityWithMetadata? {
    val container = getContainer()
    val readSnapshotDocumentResponse = container.queryItems(
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Snapshot}¥" AND x.entityId = ¥"${id}¥" ORDER BY x.version DESC",
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(id)).setMaxBufferedItemCount(1),
    EventSourcingDocument::class.java
    )
    val snapshotDataModel = readSnapshotDocumentResponse.map(SnapshotDataModel.Companion::from).firstOrNull()
    val readEntityDocumentResponse = container.readItem(id, PartitionKey(id), EventSourcingDocument::class.java)
    val entityDocument = readEntityDocumentResponse.item
    val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }

    View full-size slide

  67. override fun > find(clazz: Class, id: String): EntityWithMetadata? {
    val container = getContainer()
    val readSnapshotDocumentResponse = container.queryItems(
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Snapshot}¥" AND x.entityId = ¥"${id}¥" ORDER BY x.version DESC",
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(id)).setMaxBufferedItemCount(1),
    EventSourcingDocument::class.java
    )
    val snapshotDataModel = readSnapshotDocumentResponse.map(SnapshotDataModel.Companion::from).firstOrNull()
    val readEntityDocumentResponse = container.readItem(id, PartitionKey(id), EventSourcingDocument::class.java)
    val entityDocument = readEntityDocumentResponse.item
    val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }

    View full-size slide

  68. override fun > find(clazz: Class, id: String): EntityWithMetadata? {
    val container = getContainer()
    val readSnapshotDocumentResponse = container.queryItems(
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Snapshot}¥" AND x.entityId = ¥"${id}¥" ORDER BY x.version DESC",
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(id)).setMaxBufferedItemCount(1),
    EventSourcingDocument::class.java
    )
    val snapshotDataModel = readSnapshotDocumentResponse.map(SnapshotDataModel.Companion::from).firstOrNull()
    val readEntityDocumentResponse = container.readItem(id, PartitionKey(id), EventSourcingDocument::class.java)
    val entityDocument = readEntityDocumentResponse.item
    val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }

    View full-size slide

  69. override fun > find(clazz: Class, id: String): EntityWithMetadata? {
    val container = getContainer()
    val readSnapshotDocumentResponse = container.queryItems(
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Snapshot}¥" AND x.entityId = ¥"${id}¥" ORDER BY x.version DESC",
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(id)).setMaxBufferedItemCount(1),
    EventSourcingDocument::class.java
    )
    val snapshotDataModel = readSnapshotDocumentResponse.map(SnapshotDataModel.Companion::from).firstOrNull()
    val readEntityDocumentResponse = container.readItem(id, PartitionKey(id), EventSourcingDocument::class.java)
    val entityDocument = readEntityDocumentResponse.item
    val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }

    View full-size slide

  70. override fun > find(clazz: Class, id: String): EntityWithMetadata? {
    val container = getContainer()
    val readSnapshotDocumentResponse = container.queryItems(
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Snapshot}¥" AND x.entityId = ¥"${id}¥" ORDER BY x.version DESC",
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(id)).setMaxBufferedItemCount(1),
    EventSourcingDocument::class.java
    )
    val snapshotDataModel = readSnapshotDocumentResponse.map(SnapshotDataModel.Companion::from).firstOrNull()
    val readEntityDocumentResponse = container.readItem(id, PartitionKey(id), EventSourcingDocument::class.java)
    val entityDocument = readEntityDocumentResponse.item
    val entity = EntityDataModel.from(entityDocument)
    val getEventsQuery = if (snapshotDataModel != null) {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.version > ${entity.version} AND x.entityId = ¥"${en
    } else {
    "SELECT * FROM x WHERE x.type = ¥"${DocumentType.Event}¥" AND x.entityId = ¥"${entity.id}¥" ORDER BY x.version"
    }
    val eventDocuments = container.queryItems(
    getEventsQuery,
    CosmosQueryRequestOptions().setPartitionKey(PartitionKey(entity.id)),
    EventSourcingDocument::class.java
    )
    val events = eventDocuments.map(EventDataModel.Companion::from).map {
    val eventClazz = Class.forName(it.eventType)
    val event = Gson().fromJson(it.payload, eventClazz) as Event
    EventWithMetaData(event, it.version)
    }

    View full-size slide

  71. if (snapshotDataModel == null && events.isEmpty()) {
    return null
    }
    val version = if (events.isEmpty()) {
    entity.version
    } else {
    events.last().version
    }
    val idAndVersion = EntityIdAndVersion(id, version)
    val recreatedAggregate = if (snapshotDataModel != null) {
    val snapshotClazz = Class.forName(snapshotDataModel.snapshotType)
    val snapshot = Gson().fromJson(snapshotDataModel.snapshotData, snapshotClazz) as Snapshot
    val aggregate = snapshotManager.recreateFromSnapshot(clazz, snapshot)
    Aggregate.applyEvents(aggregate, events.map { it.event })
    } else {
    Aggregate.recreate(clazz, events.map { it.event })
    }
    return EntityWithMetadata(
    idAndVersion,
    events,
    recreatedAggregate,
    snapshotDataModel?.entityVersion.toString()
    )
    }

    View full-size slide

  72. if (snapshotDataModel == null && events.isEmpty()) {
    return null
    }
    val version = if (events.isEmpty()) {
    entity.version
    } else {
    events.last().version
    }
    val idAndVersion = EntityIdAndVersion(id, version)
    val recreatedAggregate = if (snapshotDataModel != null) {
    val snapshotClazz = Class.forName(snapshotDataModel.snapshotType)
    val snapshot = Gson().fromJson(snapshotDataModel.snapshotData, snapshotClazz) as Snapshot
    val aggregate = snapshotManager.recreateFromSnapshot(clazz, snapshot)
    Aggregate.applyEvents(aggregate, events.map { it.event })
    } else {
    Aggregate.recreate(clazz, events.map { it.event })
    }
    return EntityWithMetadata(
    idAndVersion,
    events,
    recreatedAggregate,
    snapshotDataModel?.entityVersion.toString()
    )
    }

    View full-size slide

  73. if (snapshotDataModel == null && events.isEmpty()) {
    return null
    }
    val version = if (events.isEmpty()) {
    entity.version
    } else {
    events.last().version
    }
    val idAndVersion = EntityIdAndVersion(id, version)
    val recreatedAggregate = if (snapshotDataModel != null) {
    val snapshotClazz = Class.forName(snapshotDataModel.snapshotType)
    val snapshot = Gson().fromJson(snapshotDataModel.snapshotData, snapshotClazz) as Snapshot
    val aggregate = snapshotManager.recreateFromSnapshot(clazz, snapshot)
    Aggregate.applyEvents(aggregate, events.map { it.event })
    } else {
    Aggregate.recreate(clazz, events.map { it.event })
    }
    return EntityWithMetadata(
    idAndVersion,
    events,
    recreatedAggregate,
    snapshotDataModel?.entityVersion.toString()
    )
    }

    View full-size slide

  74. if (snapshotDataModel == null && events.isEmpty()) {
    return null
    }
    val version = if (events.isEmpty()) {
    entity.version
    } else {
    events.last().version
    }
    val idAndVersion = EntityIdAndVersion(id, version)
    val recreatedAggregate = if (snapshotDataModel != null) {
    val snapshotClazz = Class.forName(snapshotDataModel.snapshotType)
    val snapshot = Gson().fromJson(snapshotDataModel.snapshotData, snapshotClazz) as Snapshot
    val aggregate = snapshotManager.recreateFromSnapshot(clazz, snapshot)
    Aggregate.applyEvents(aggregate, events.map { it.event })
    } else {
    Aggregate.recreate(clazz, events.map { it.event })
    }
    return EntityWithMetadata(
    idAndVersion,
    events,
    recreatedAggregate,
    snapshotDataModel?.entityVersion.toString()
    )
    }

    View full-size slide