Created
December 4, 2019 23:11
-
-
Save ShahOdin/d0df23f0f8ef9eae34d230ebde93c696 to your computer and use it in GitHub Desktop.
Data aggregation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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