Skip to content

Instantly share code, notes, and snippets.

@erikrozendaal
Created February 1, 2011 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikrozendaal/806373 to your computer and use it in GitHub Desktop.
Save erikrozendaal/806373 to your computer and use it in GitHub Desktop.
Code excerpts for the immutable domain model blogs
trait AggregateRoot[Event] {
protected def applyEvent: Event => Unit
def uncommittedEvents: Iterable[Event] = _uncommittedEvents
def markCommitted = _uncommittedEvents.clear
def loadFromHistory(history: Iterable[Event]) = history.foreach(applyEvent)
protected def record(event: Event) {
applyEvent(event)
_uncommittedEvents += event
}
private val _uncommittedEvents = mutable.Queue[Event]()
}
trait AggregateRoot[AR <: AggregateRoot[AR, Event], Event] extends EventSourced[AR, Event] {
def uncommittedEvents: List[Event]
def markCommitted: AR
}
trait AggregateFactory[AR <: AggregateRoot[AR, Event], Event] extends EventSourced[AR, Event] {
def loadFromHistory(history: Iterable[Event]): AR = {
var aggregate = applyEvent(history.head)
for (event <- history.tail)
aggregate = aggregate.applyEvent(event)
return aggregate.markCommitted
}
}
protected def applyEvent = {
// [... code omitted ...]
case event: InvoiceSent =>
sent_? = true
dueDate = Some(event.dueDate)
// [... code omitted ...]
}
class MutableCounter(var current: Int) {
def increment {
current += 1
}
}
class ImmutableCounter(val current: Int) {
def increment = new ImmutableCounter(current + 1)
}
trait EventSourced[ES <: EventSourced[ES, Event], Event] {
def applyEvent: Event => ES
def unhandled(event: Event) = error("event " + event + " does not apply to " + this)
}
case class InvoiceItem(id: Int, description: String, amount: BigDecimal)
sealed trait InvoiceEvent {
val invoiceId: Int
}
case class InvoiceCreated(invoiceId: Int) extends InvoiceEvent
case class InvoiceRecipientChanged(invoiceId: Int, recipient: Option[String]) extends InvoiceEvent
case class InvoiceItemAdded(invoiceId: Int, item: InvoiceItem, totalAmount: BigDecimal) extends InvoiceEvent
case class InvoiceItemRemoved(invoiceId: Int, item: InvoiceItem, totalAmount: BigDecimal) extends InvoiceEvent
case class InvoiceSent(invoiceId: Int, sentDate: LocalDate, dueDate: LocalDate) extends InvoiceEvent
case class InvoiceReminderSent(invoiceId: Int, reminderDate: LocalDate) extends InvoiceEvent
case class InvoicePaymentReceived(invoiceId: Int, paymentDate: LocalDate) extends InvoiceEvent
object Invoice extends AggregateFactory[Invoice, InvoiceEvent] {
def create(invoiceId: Int) = applyEvent(InvoiceCreated(invoiceId))
def applyEvent = {
case event: InvoiceCreated => Invoice(event :: Nil, event.invoiceId)
case event => unhandled(event)
}
}
def send {
require(!sent_?, "invoice already sent")
require(readyToSend_?, "recipient and items must be specified before sending")
val now = new LocalDate
record(InvoiceSent(id, sentDate = now, dueDate = now.plusDays(14)))
}
"ready to send invoice" should {
"generate invoice sent event" in {
val invoice = new Invoice
invoice.loadFromHistory(Seq(
InvoiceCreated(1),
InvoiceRecipientChanged(1, Some("Erik")),
InvoiceItemAdded(1, InvoiceItem(1, "Food", 2.95), 2.95)))
invoice.send
invoice.uncommittedEvents must contain(
InvoiceSent(1,
sentDate = new LocalDate(2011, 1, 29),
dueDate = new LocalDate(2011, 2, 12)))
}
}
@OneToMany(cascade = Array(CascadeType.ALL))
@OrderBy
private var _items: List[InvoiceItem] = new ArrayList
@Basic(optional = false)
private var _totalAmount: BigDecimal = BigDecimal.ZERO
@Temporal(TemporalType.DATE)
private var _sentDate: Date = _
def sent_? = _sentDate != null
def removeItem(index: Int) {
require(!sent_?, "items cannot be changed after invoice is sent")
val item = _items.remove(index)
_totalAmount = _totalAmount.subtract(item.amount)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment