Skip to content

Instantly share code, notes, and snippets.

@ShahOdin
Created December 4, 2019 23:11
Show Gist options
  • Save ShahOdin/d0df23f0f8ef9eae34d230ebde93c696 to your computer and use it in GitHub Desktop.
Save ShahOdin/d0df23f0f8ef9eae34d230ebde93c696 to your computer and use it in GitHub Desktop.
Data aggregation
import cats.data.{NonEmptyChain, NonEmptyList, Validated, ValidatedNec}
import cats.syntax.apply._
import cats.syntax.option._
import cats.syntax.either._
import cats.instances.all._
//just a demonstration of aggregating partial updates in a flattened model
object partialUpdateBasicDemo extends App {
type A = String
type B = String
type C = String
case class DatabaseModel(a: Option[A], b: Option[B], c: Option[C])
case class ApiModel(a: A, b: B, c: C)
object ApiModel {
def fromDatabaseModel(dbm: DatabaseModel): Option[ApiModel] =
(dbm.a, dbm.b, dbm.c)
.mapN(ApiModel.apply)
}
var databaseValues: NonEmptyList[DatabaseModel] = NonEmptyList.one(DatabaseModel(None, None, None))
def getLastValue = databaseValues.head
def updateA(maybeA: Option[A]) = {
databaseValues = getLastValue.copy(a = maybeA) :: databaseValues
}
def updateB(maybeB: Option[B]) = {
databaseValues = getLastValue.copy(b = maybeB) :: databaseValues
}
def updateC(maybeC: Option[C]) = {
databaseValues = getLastValue.copy(c = maybeC) :: databaseValues
}
updateA("Alpha".some)
updateB("beta".some)
updateC("Gamma".some)
updateB("Beta".some)
updateC(none)
def printValuesOverTime = databaseValues
.reverse
.map(
ApiModel.fromDatabaseModel
).map(println)
def printDebugValuesOverTime = databaseValues
.reverse
.map(println)
println("\n***API values over time***\n")
printValuesOverTime
println("\n***Debug values over time***\n")
printDebugValuesOverTime
}
//above + showing reasons for values failing validation.
object partialUpdateDemo2 extends App {
type A = String
type B = String
type C = String
type Reason = String
type ValueOrWhyMissing[T] = Either[Reason, T]
case class DatabaseModel(a: ValueOrWhyMissing[A], b: ValueOrWhyMissing[B], c: ValueOrWhyMissing[C])
case class ApiModel(a: A, b: B, c: C)
object ApiModel {
def fromDataBaseModel(dbm: DatabaseModel): ValidatedNec[Reason, ApiModel] = {
(dbm.a.toValidatedNec, dbm.b.toValidatedNec, dbm.c.toValidatedNec)
.mapN(ApiModel.apply)
}
}
var databaseValues: NonEmptyList[DatabaseModel] = NonEmptyList.one(DatabaseModel("A: Nothing in db".asLeft, "B: Nothing in db".asLeft, "C: Nothing in db".asLeft))
def getLastValue = databaseValues.head
def updateA(maybeA: ValueOrWhyMissing[A]) = {
databaseValues = getLastValue.copy(a = maybeA) :: databaseValues
}
def updateB(maybeB: ValueOrWhyMissing[B]) = {
databaseValues = getLastValue.copy(b = maybeB) :: databaseValues
}
def updateC(maybeC: ValueOrWhyMissing[C]) = {
databaseValues = getLastValue.copy(c = maybeC) :: databaseValues
}
updateA("Alpha".asRight)
updateB("beta".asRight)
updateC("Gamma".asRight)
updateB("Beta".asRight)
updateC("C: failed validation".asLeft)
def printValuesOverTime: NonEmptyList[Unit] = databaseValues
.reverse
.map(
ApiModel.fromDataBaseModel
).map(_.toEither)
.map(println)
def printDebugValuesOverTime: NonEmptyList[Unit] = databaseValues
.reverse
.map(println)
println("\n***API values over time***\n")
printValuesOverTime
println("\n***Debug values over time***\n")
printDebugValuesOverTime
}
//all of the above in a nested model. in order to connect the separate non-flattened entries, we need a timestamp.
object partialUpdateDemo3 extends App {
type A1 = String
type A2 = String
case class ATotal(a1: A1, a2: A2)
type B1 = String
type B2 = String
case class BTotal(b1: B1, b2: B2)
type Reason = String
type TimeCount = Long
type ValueOrWhyMissing[A] = Either[Reason, A]
case class DatabaseModelA(a1: ValueOrWhyMissing[A1], a2: ValueOrWhyMissing[A2], timeCount: TimeCount)
case class DatabaseModelB(b1: ValueOrWhyMissing[B1], b2: ValueOrWhyMissing[B2], timeCount: TimeCount)
case class ApiModel(a: ATotal, b: BTotal, count: TimeCount)
object ApiModel {
def fromDataBaseModel(dbmA: DatabaseModelA, dbmB: DatabaseModelB, count: TimeCount): Validated[(NonEmptyChain[Reason], Long), ApiModel] = {
val maybeATotal: ValidatedNec[Reason, ATotal] = (dbmA.a1.toValidatedNec, dbmA.a2.toValidatedNec)
.mapN(ATotal.apply)
val maybeBTotal: ValidatedNec[Reason, BTotal] = (dbmB.b1.toValidatedNec, dbmB.b2.toValidatedNec)
.mapN(BTotal.apply)
(maybeATotal, maybeBTotal)
.mapN{
ApiModel.apply(_, _, count)
}
.leftMap(nec => (nec, count))
}
}
var databaseModelAvalues: NonEmptyList[DatabaseModelA] = NonEmptyList.one(
DatabaseModelA("A1: no value Here".asLeft, "A2: no value here".asLeft, 0)
)
var databaseModelBvalues: NonEmptyList[DatabaseModelB] = NonEmptyList.one(
DatabaseModelB("B1: no value here".asLeft, "B2: no value here".asLeft, 0)
)
def updateA1(maybeA: ValueOrWhyMissing[A1], count: TimeCount) = {
databaseModelAvalues = databaseModelAvalues.head.copy(a1 = maybeA, timeCount = count) :: databaseModelAvalues
}
def updateA2(maybeA: ValueOrWhyMissing[A2], count: TimeCount) = {
databaseModelAvalues = databaseModelAvalues.head.copy(a2 = maybeA, timeCount = count) :: databaseModelAvalues
}
def updateB1(maybeB1: ValueOrWhyMissing[B1], count: TimeCount) = {
databaseModelBvalues = databaseModelBvalues.head.copy(b1 = maybeB1, timeCount = count) :: databaseModelBvalues
}
def updateB2(maybeB2: ValueOrWhyMissing[B2], count: TimeCount) = {
databaseModelBvalues = databaseModelBvalues.head.copy(b2 = maybeB2, timeCount = count) :: databaseModelBvalues
}
updateA1("alpha1".asRight, 1)
updateA2("alpha2".asRight, 3)
updateB1("beta1".asRight, 2)
updateB2("beta2".asRight, 6)
def aTotalAtTime(count: TimeCount): DatabaseModelA = databaseModelAvalues
.filter(_.timeCount <= count)
.maxBy(_.timeCount)
def bTotalAtTime(count: TimeCount): DatabaseModelB = databaseModelBvalues
.filter(_.timeCount <= count)
.maxBy(_.timeCount)
def aAndBEntries: NonEmptyList[(DatabaseModelA, DatabaseModelB, Long)] = {
databaseModelAvalues.map(_.timeCount) ::: databaseModelBvalues.map(_.timeCount)
}.distinct
.map( t =>
(
aTotalAtTime(t),
bTotalAtTime(t),
t
)
)
def printValuesOverTime: NonEmptyList[Unit] = {
aAndBEntries
.map{
case (a, b, t) =>
ApiModel.fromDataBaseModel(a, b, t)
}
.map(_.toEither)
.sortBy(
_.bimap(_._2, _.count)
)
.map(println)
}
def printDebugValuesOverTime: NonEmptyList[Unit] = {
aAndBEntries.map{
case x => (x._1.a1, x._1.a2, x._2.b1, x._2.b2, x._3)
}
.sortBy(_._5)
.map(println)
}
println("\n***API values over time***\n")
printValuesOverTime
println("\n***Debug values over time***\n")
printDebugValuesOverTime
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment