Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed repository to use future in ch6 #41

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ project/plugins/project/
# Scala-IDE specific
.scala_dependencies
.worksheet

# IntelliJ specific
.idea
13 changes: 5 additions & 8 deletions src/main/scala/frdomain/ch6/domain/app/app.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,33 @@ import scalaz._
import Scalaz._
import Kleisli._
import scala.concurrent._
import ExecutionContext.Implicits.global

import service.interpreter.{ AccountService, InterestPostingService, ReportingService }
import repository.interpreter.AccountRepositoryInMemory
import service.{ Checking, Savings }
import model.common._
import model.Account

object App {

import AccountService._
import InterestPostingService._
import ReportingService._
import ExecutionContext.Implicits.global

val opens =
val opens =
for {
_ <- open("a1234", "a1name", None, None, Checking)
_ <- open("a2345", "a2name", None, None, Checking)
_ <- open("a3456", "a3name", BigDecimal(5.8).some, None, Savings)
_ <- open("a4567", "a4name", None, None, Checking)
_ <- open("a5678", "a5name", BigDecimal(2.3).some, None, Savings)
} yield (())
} yield ()

val credits =
val credits =
for {
_ <- credit("a1234", 1000)
_ <- credit("a2345", 2000)
_ <- credit("a3456", 3000)
_ <- credit("a4567", 4000)
} yield (())
} yield ()

val c = for {
_ <- opens
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/frdomain/ch6/domain/app/app2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ package app

import scalaz._
import Scalaz._
import \/._

import repository.interpreter.AccountRepositoryInMemory
import model.common._
import model.{ Account, Balance }
import model.{Account, Balance}
import Account._
import frdomain.ch6.domain.service.Valid

object App2 {

import AccountRepositoryInMemory._
val account = checkingAccount("a-123", "debasish ghosh", today.some, None, Balance(0)).toOption.get
import scala.concurrent.ExecutionContext.Implicits.global

val account = checkingAccount("a-123", "debasish ghosh", today.some, None, Balance()).toOption.get
val c = for {
b <- updateBalance(account, 10000)
b <- Valid(updateBalance(account, 10000))
c <- store(b)
d <- balance(c.no)
} yield d
Expand Down
22 changes: 10 additions & 12 deletions src/main/scala/frdomain/ch6/domain/model/Account.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ sealed trait Account {
final case class CheckingAccount (no: String, name: String,
dateOfOpen: Option[Date], dateOfClose: Option[Date] = None, balance: Balance = Balance()) extends Account

final case class SavingsAccount (no: String, name: String, rateOfInterest: Amount,
final case class SavingsAccount (no: String, name: String, rateOfInterest: Amount,
dateOfOpen: Option[Date], dateOfClose: Option[Date] = None, balance: Balance = Balance()) extends Account

object Account {
private def validateAccountNo(no: String) =
if (no.isEmpty || no.size < 5) s"Account No has to be at least 5 characters long: found $no".failureNel[String]
private def validateAccountNo(no: String) =
if (no.isEmpty || no.size < 5) s"Account No has to be at least 5 characters long: found $no".failureNel[String]
else no.successNel[String]

private def validateOpenCloseDate(od: Date, cd: Option[Date]) = cd.map { c =>
private def validateOpenCloseDate(od: Date, cd: Option[Date]) = cd.map { c =>
if (c before od) s"Close date [$c] cannot be earlier than open date [$od]".failureNel[(Option[Date], Option[Date])]
else (od.some, cd).successNel[String]
}.getOrElse { (od.some, cd).successNel[String] }
Expand All @@ -45,26 +45,26 @@ object Account {
if (rate <= BigDecimal(0)) s"Interest rate $rate must be > 0".failureNel[BigDecimal]
else rate.successNel[String]

def checkingAccount(no: String, name: String, openDate: Option[Date], closeDate: Option[Date],
balance: Balance): \/[NonEmptyList[String], Account] = {
def checkingAccount(no: String, name: String, openDate: Option[Date], closeDate: Option[Date],
balance: Balance): \/[NonEmptyList[String], Account] = {

val od = openDate.getOrElse(today)

(
validateAccountNo(no) |@|
validateAccountNo(no) |@|
validateOpenCloseDate(openDate.getOrElse(today), closeDate)
) { (n, d) =>
CheckingAccount(n, name, d._1, d._2, balance)
}.disjunction
}

def savingsAccount(no: String, name: String, rate: BigDecimal, openDate: Option[Date],
closeDate: Option[Date], balance: Balance): \/[NonEmptyList[String], Account] = {
def savingsAccount(no: String, name: String, rate: BigDecimal, openDate: Option[Date],
closeDate: Option[Date], balance: Balance): \/[NonEmptyList[String], Account] = {

val od = openDate.getOrElse(today)

(
validateAccountNo(no) |@|
validateAccountNo(no) |@|
validateOpenCloseDate(openDate.getOrElse(today), closeDate) |@|
validateRate(rate)
) { (n, d, r) =>
Expand Down Expand Up @@ -110,5 +110,3 @@ object Account {
case _ => None
}
}


9 changes: 9 additions & 0 deletions src/main/scala/frdomain/ch6/domain/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,13 @@ import Scalaz._

package object service {
type Valid[A] = EitherT[Future, NonEmptyList[String], A]

object Valid {
implicit val validAppl = new Applicative[Valid] {
override def point[A](a: => A): Valid[A] = EitherT(Future.successful(a.right))
override def ap[A, B](fa: => Valid[A])(f: => Valid[(A) => B]): Valid[B] = ???
}

def apply[A](block: => NonEmptyList[String] \/ A): Valid[A] = EitherT { Future.successful(block) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ package domain
package repository

import java.util.Date
import scalaz._
import Scalaz._
import \/._
import model.{ Account, Balance }

trait AccountRepository {
def query(no: String): \/[NonEmptyList[String], Option[Account]]
def store(a: Account): \/[NonEmptyList[String], Account]
def balance(no: String): \/[NonEmptyList[String], Balance] = query(no) match {
case \/-(Some(a)) => a.balance.right
case \/-(None) => NonEmptyList(s"No account exists with no $no").left[Balance]
case a @ -\/(_) => a
import frdomain.ch6.domain.model.{Account, Balance}
import frdomain.ch6.domain.service.Valid

import scala.concurrent.ExecutionContext.Implicits.global
import scalaz.NonEmptyList
import scalaz.Scalaz._

trait AccountRepository {
def query(no: String): Valid[Option[Account]]
def store(a: Account): Valid[Account]
def query(openedOn: Date): Valid[Seq[Account]]
def all: Valid[Seq[Account]]

def balance(no: String): Valid[Balance] = query(no).flatMap { maybeAcc =>
Valid {
maybeAcc match {
case Some(acc) => acc.balance.right
case None => NonEmptyList(s"No account exists with no $no").left
}
}
}
def query(openedOn: Date): \/[NonEmptyList[String], Seq[Account]]
def all: \/[NonEmptyList[String], Seq[Account]]
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package frdomain.ch6
package domain
package repository
package interpreter

import java.util.Date

import frdomain.ch6.domain.model.Account
import frdomain.ch6.domain.service.Valid
import frdomain.ch6.domain.service.Valid._

import scala.collection.mutable.{Map => MMap}
import scalaz.Scalaz._

trait AccountRepositoryInMemory extends AccountRepository {
lazy val repo = MMap.empty[String, Account]

override def query(no: String): Valid[Option[Account]] = repo.get(no).pure[Valid]

override def store(a: Account): Valid[Account] = {
repo += ((a.no, a))
a.pure[Valid]
}

override def query(openedOn: Date): Valid[Seq[Account]] =
repo.values.filter(_.dateOfOpen == openedOn).toSeq.pure[Valid]

override def all: Valid[Seq[Account]] = repo.values.toSeq.pure[Valid]
}

object AccountRepositoryInMemory extends AccountRepositoryInMemory

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ trait AccountService[Account, Amount, Balance] {
def transfer(from: String, to: String, amount: Amount): AccountOperation[(Account, Account)] = for {
a <- debit(from, amount)
b <- credit(to, amount)
} yield ((a, b))
} yield (a, b)
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,48 @@ package domain
package service
package interpreter

import java.util.{ Date, Calendar }
import java.util.{Date, Calendar}

import scalaz._
import Scalaz._
import \/._
import Kleisli._

import scala.concurrent._
import ExecutionContext.Implicits.global

import model.{ Account, Balance }
import model.{Account, Balance}
import model.common._
import repository.AccountRepository

import scala.concurrent._
import ExecutionContext.Implicits.global

class AccountServiceInterpreter extends AccountService[Account, Amount, Balance] {

def open(no: String,
name: String,
def open(no: String,
name: String,
rate: Option[BigDecimal],
openingDate: Option[Date],
accountType: AccountType) = kleisli[Valid, AccountRepository, Account] { (repo: AccountRepository) =>

EitherT {
Future {
repo.query(no) match {
case \/-(Some(a)) => NonEmptyList(s"Already existing account with no $no").left[Account]
case \/-(None) => accountType match {
case Checking => Account.checkingAccount(no, name, openingDate, None, Balance()).flatMap(repo.store)
case Savings => rate map { r =>
Account.savingsAccount(no, name, r, openingDate, None, Balance()).flatMap(repo.store)
} getOrElse {
NonEmptyList(s"Rate needs to be given for savings account").left[Account]
}
}
case a @ -\/(_) => a
repo.query(no) flatMap {
case Some(_) => Valid(NonEmptyList(s"Already existing account with no $no").left[Account])
case None => accountType match {
case Checking => Valid(Account.checkingAccount(no, name, openingDate, None, Balance())).flatMap(repo.store)
case Savings => rate map { r =>
Valid(Account.savingsAccount(no, name, r, openingDate, None, Balance())).flatMap(repo.store)
} getOrElse {
Valid(NonEmptyList(s"Rate needs to be given for savings account").left[Account])
}
}
}
}

def close(no: String, closeDate: Option[Date]) = kleisli[Valid, AccountRepository, Account] { (repo: AccountRepository) =>
EitherT {
Future {
repo.query(no) match {
case \/-(None) => NonEmptyList(s"Account $no does not exist").left[Account]
case \/-(Some(a)) =>
val cd = closeDate.getOrElse(today)
Account.close(a, cd).flatMap(repo.store)
case a @ -\/(_) => a
}
def close(no: String, closeDate: Option[Date]) = kleisli[Valid, AccountRepository, Account] {
(repo: AccountRepository) =>
repo.query(no).flatMap {
case None => Valid(NonEmptyList(s"Account $no does not exist").left[Account])
case Some(a) =>
val cd = closeDate.getOrElse(today)
Valid(Account.close(a, cd)).flatMap(repo.store)
}
}
}

def debit(no: String, amount: Amount) = up(no, amount, D)
Expand All @@ -64,26 +54,20 @@ class AccountServiceInterpreter extends AccountService[Account, Amount, Balance]
private case object D extends DC
private case object C extends DC

private def up(no: String, amount: Amount, dc: DC): AccountOperation[Account] = kleisli[Valid, AccountRepository, Account] { (repo: AccountRepository) =>
EitherT {
Future {
repo.query(no) match {
case \/-(None) => NonEmptyList(s"Account $no does not exist").left[Account]
case \/-(Some(a)) => dc match {
case D => Account.updateBalance(a, -amount).flatMap(repo.store)
case C => Account.updateBalance(a, amount).flatMap(repo.store)
}
case a @ -\/(_) => a
private def up(no: String, amount: Amount, dc: DC): AccountOperation[Account] = kleisli[Valid, AccountRepository, Account] {
(repo: AccountRepository) =>
repo.query(no) flatMap {
case None => Valid(NonEmptyList(s"Account $no does not exist").left[Account])
case Some(a) => dc match {
case D => Valid(Account.updateBalance(a, -amount)).flatMap(repo.store)
case C => Valid(Account.updateBalance(a, amount)).flatMap(repo.store)
}
}
}
}

def balance(no: String) =
kleisli[Valid, AccountRepository, Balance] { (repo: AccountRepository) =>
EitherT {
Future { repo.balance(no) }
}
def balance(no: String) =
kleisli[Valid, AccountRepository, Balance] {
(repo: AccountRepository) => repo.balance(no)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,10 @@ import model.common._

class ReportingServiceInterpreter extends ReportingService[Amount] {

def balanceByAccount: ReportOperation[Seq[(String, Amount)]] = kleisli[Valid, AccountRepository, Seq[(String, Amount)]] { (repo: AccountRepository) =>
EitherT {
Future {
repo.all match {
case \/-(as) => as.map(a => (a.no, a.balance.amount)).right
case a @ -\/(_) => a
}
}
def balanceByAccount: ReportOperation[Seq[(String, Amount)]] =
kleisli[Valid, AccountRepository, Seq[(String, Amount)]] { (repo: AccountRepository) =>
repo.all map { as => as.map(a => (a.no, a.balance.amount)) }
}
}
}
}

object ReportingService extends ReportingServiceInterpreter