Skip to content

Instantly share code, notes, and snippets.

@jorgebo10
Created February 23, 2023 13:47
Show Gist options
  • Save jorgebo10/c7fdfde6b69e6e23b81b2432cd09e07a to your computer and use it in GitHub Desktop.
Save jorgebo10/c7fdfde6b69e6e23b81b2432cd09e07a to your computer and use it in GitHub Desktop.
package funcore
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import io.travelx.flights.itineraryreservations.core.domain.model.*
sealed interface ReservationDomainError
@JvmInline
value class ReservationNotFoundDomainError private constructor(val reservationId: ReservationId) : ReservationDomainError {
companion object {
fun fromId(id: String) = ReservationNotFoundDomainError(ReservationId(id))
fun fromReservationId(reservationId: ReservationId) = ReservationNotFoundDomainError(reservationId)
}
}
object NotEnoughFundsDomainError : ReservationDomainError
object InvalidStatusDomainError : ReservationDomainError
object NoDomainError : ReservationDomainError
fun payReservation(reservation: Reservation): Either<ReservationDomainError, Reservation> =
if (reservation.reservationId.value == "invalidStatusError") {
InvalidStatusDomainError.left()
} else {
reservation.copy(status = ReservationStatus.PAID).right()
}
fun createReservation(reservationId: ReservationId) =
Reservation(
reservationId,
Pnr("XDFG"),
PurchaseId("12"),
null,
ReservationStatus.BOOKED,
null,
Itinerary(
ItineraryId("eee"),
emptyList(),
priceInfo = PriceInfo(
adults = PaxPrice(
passengerType = "ADT",
totalFare = "1",
baseFare = "1",
baseTax = "1",
ptcTotal = "1",
totalTax = "0",
quantity = 1U,
fareType = "PUB",
extraTaxes = emptyList(),
policies = emptyList(),
surcharges = emptyList(),
corporateIds = listOf("corporateId"),
),
currency = "ARS"
),
createdAt = "",
validatingCarrier = "AR",
provider = "FO",
flightType = "ONE_WAY",
isDomestic = false,
includedBaggageOnly = false,
),
contextData = ContextData(),
fareRules = Rules(emptyMap())
)
@file:OptIn(ExperimentalCoroutinesApi::class)
package mutableshell
import arrow.core.*
import io.travelx.flights.itineraryreservations.core.domain.*
import io.travelx.flights.itineraryreservations.core.domain.model.ReservationId
import io.travelx.flights.itineraryreservations.core.domain.model.ReservationStatus.*
import io.travelx.flights.itineraryreservations.infrastructure.repository.funcore.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
internal class EffectTest {
@Test
fun router() = runTest {
/*
This creates a description of a program that ONLY when executed will trigger a side effect.
e.g.: an exception or access to a database
So in a functional architecture it is part of the functional core, however the point where it is
executed belongs to the imperative shell where exceptions will be handled
iterate over these reservationIds for testing different cases
storeException notFoundError ok storeException getByIdException invalidStatusError notEnoughFundsError invalidStatusError
*/
PayReservationUseCase(ReservationRepositoryImpl(), PaymentApi())
.invoke(ReservationId("storeException"))
.fold(
{
throw IllegalArgumentException("Problem details status=500 title=reservations/500", it)
}, {
when (it) {
is ReservationNotFoundDomainError -> throw IllegalArgumentException("Problem details status=404 title=reservations/404")
NoDomainError -> TODO("this might never happen")
InvalidStatusDomainError -> throw IllegalArgumentException("Problem details status=400 title=reservations/400 detail=invalidStatus")
NotEnoughFundsDomainError -> throw IllegalArgumentException("Problem details status=400 title=reservations/400 detail=notEnoughFunds")
}
},
{
println("reservation updated")
})
}
}
package funcore
import arrow.core.continuations.Effect
import arrow.core.continuations.effect
import io.travelx.flights.itineraryreservations.core.domain.model.Reservation
import io.travelx.flights.itineraryreservations.core.domain.model.ReservationId
class ReservationRepositoryImpl {
fun getById(reservationId: ReservationId): Effect<ReservationDomainError, Reservation> = effect {
when (reservationId.value) {
"notFoundError" -> {
shift(ReservationNotFoundDomainError.fromReservationId(reservationId))
}
"ok" -> {
createReservation(reservationId)
}
"storeException" -> {
createReservation(reservationId)
}
"getByIdException" -> {
throw java.lang.IllegalArgumentException("getByIdException")
}
"invalidStatusError" -> {
createReservation(reservationId)
}
"notEnoughFundsError" -> {
createReservation(reservationId)
}
else -> {
throw IllegalArgumentException("Exception")
}
}
}
fun store(reservation: Reservation): Effect<Unit, Unit> = effect {
if (reservation.reservationId.value == "storeException") {
throw IllegalArgumentException("Store exception")
} else
Unit
}
}
class PaymentApi {
fun registerPaymentForReservation(reservation: Reservation): Effect<ReservationDomainError, String> = effect {
if (reservation.reservationId.value == "notEnoughFundsError") {
shift(NotEnoughFundsDomainError)
} else {
"Ok"
}
}
}
class PayReservationUseCase(
private val repository: ReservationRepositoryImpl,
private val paymentApi: PaymentApi,
) {
fun invoke(reservationId: ReservationId): Effect<ReservationDomainError, Unit> = effect {
val reservation = repository.getById(reservationId).bind()
val paidReservation = payReservation(reservation).bind()
paymentApi.registerPaymentForReservation(paidReservation).bind()
repository.store(paidReservation).handleError { shift(NoDomainError) }.bind()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment