2016-12-08

More Longevity Awesomeness with Macro Annotations!

I just got longevity release 0.18 out and wow, is it awesome! If you've looked at longevity before, you will be amazed at how easy it has become to start persisting your domain objects. And the best part is that everything persistence related is tucked away in the annotations. Your domain classes are completely free of persistence concerns, expressing your domain model perfectly, and ready for use in all portions of your application. Of course, we also provide a complete persistence layer for you, saving you countless hours of development effort!

See for yourself, here is a simple example demonstrating how easy it is to get started:

package domainModel {
  import longevity.model.annotations._

  // first, define our domain classes:

  // the @persistent is the thing we want to persist in its own table.
  // the primaryKey describes how we want to retrieve the objects.

  @persistent(keySet = Set(primaryKey(User.props.username)))
  case class User(
    username: Username,
    email: Email,
    fullName: FullName)

  // @keyVal means we can retrieve users by username:

  @keyVal[User]
  case class Username(username: String)

  // a @component is a part of the object we want to persist:

  @component
  case class Email(email: String)

  @component
  case class FullName(
    last: String,
    first: String,
    title: Option[String])

  // gather all the domain classes into a domain model:

  @domainModel
  object DomainModel
}

object applicationServices extends App {
  import domainModel._
  import longevity.context.LongevityContext
  import scala.concurrent.ExecutionContext.Implicits.global

  // build the context for our domain model

  val context  = LongevityContext(DomainModel)

  // get the user repository

  val userRepo = context.repoPool[User]

  // we are now ready to start persisting users

  val username = Username("sméagol")
  val oldEmail = Email("gollum@gmail.example.com")
  val newEmail = Email("sméagol@gmail.example.com")
  val fullName = context.testDataGenerator.generate[FullName]
  val user     = User(username, oldEmail, fullName)

  // create, retrieve, update, delete

  val f = for {
    created   <- userRepo.create(user)
    retrieved <- userRepo.retrieveOne(username)
    modified   = retrieved.map(_.copy(email = newEmail))
    updated   <- userRepo.update(modified)
    deleted   <- userRepo.delete(updated)
  } yield {
    println(s"created   ${created.get}")
    println(s"retrieved ${retrieved.get}")
    println(s"updated   ${updated.get}")
    println(s"deleted   ${deleted.get}")
  }

  // wait for the CRUD ops to complete

  import scala.concurrent.Await
  import scala.concurrent.duration.Duration

  Await.result(f, Duration.Inf)

  // close db connection after CRUD ops complete

  context.repoPool.closeSession()

}
You can find this example on GitHub. It should pretty much run for you right out of the box. Try setting longevity.backEnd to Mongo or Cassandra in application.conf if you have one of them installed locally.

Writing macro annotations is so much fun! I started out writing a single macro, and ended up writing nine. My initial goal was removing the boilerplate in describing the properties of a persistent class, i.e., the ability to talk meta about members of classes like the User.username field above. With the @persistent annotation, this property is simply User.props.username. But it occurred to me that I could write a small handful of annotations that would allow for describing a domain model using nothing but the annotations.

Of course, all longevity functionality is available without using the macro annotations! But I don't see any advantage in doing so, aside from not having to enable macro paradise in your build.

Well, what a relief to get this out! I feel like a true 1.0 feature set is now in place. But because the last few releases have brought some major API changes, I plan on holding off on committing to a 1.0 release for a while, to give the changes some time to settle.

No comments:

Post a Comment