7
\$\begingroup\$

In the Java world while writing the data access layer (for CRUD) and the model layer, I have done something like this:

public abstract class AbstractDao<N extends AbstractModel>{
        protected Class<N> persistentClass;
        public N findById(String id){

                return (N)mongoOperation.findById(id, persistentClass,persistentClass.getSimpleName());

        }
}

class FlightDao extends AbstractDao<Flight>
class AirlineDaoImpl extends AbstractMongoDao<Airline>

Where Flight extends AbstractModel and ,Airline extended AbstractModel

Now calling the findById was as trivial as

Flight flight = flightDao.findById("FlightId1");

I feel this was a decent way of doing things because it kept my code DRY. And most of the model objects needed the same CRUD methods.

Now coming to Scala, Play framework 2, anorm, I would like to know what is the right way of doing something such as this.

I read that model classes should be case classes but then that prevents us from inheritance. And moreover the data access definitions were given in the companion objects. This is what I have at the moment.

case class Airline(id:Pk[Long]=NotAssigned,name:String)
case class Flight(id:Pk[Long]=NotAssigned,code:String,airlineId:Long,date:DateTime)

object Flight{
    def getById(id:Long):Option[Flight] = {
    DB.withConnection { implicit connection =>
       SQL(
        """
          select * from flight        
          where flight.id =  {id}
        """
      ).on(
        'id -> id       
      ).as(Flight.simple.singleOpt)
    } 

  }
}

object Airline{
    def getById(id:Long):Option[Airline] = {
    DB.withConnection { implicit connection =>
       SQL(
        """
          select * from airline        
          where airline.id =  {id}
        """
      ).on(
        'id -> id       
      ).as(Airline.simple.singleOpt)
    } 

  }
} 

I'm certain there must be a better way to do this. I would love to know how I could write neat / clean Scala code for this. Any pointers, design patterns and feedback is much appreciated.

\$\endgroup\$
14
\$\begingroup\$

I believe that in addition to the stated Java -> Scala OO question, that you may also be wondering about a good way to "organize" Models/DAOs/Controllers in Play 2. So I will incorporate that in my answer.

I would have asked in a comment, but I don't seem to have the rep yet...

A few notes before we get into an answer:

  • fedesilvia is correct, that case classes can extend traits and/or classes.
  • Model classes generally should be case classes. They're a nice, easy-to-use construct that provides your "equals" method, "toString" method, and "hashcode" method. Also, many libraries have provided great mechanisms for working with case classes for a variety of purposes. Not to mention matching, immutable by default, etc...

Let's take a look at how to organize this from top to bottom.

Controllers

As you know, Play's Router sends requests in from the HTTP client to your Controller. It is my opinion that the Controllers then have 3 core responsibilities:

  1. Deserialize the request.
  2. Serialize the response.
  3. Handle any errors appropriately.

Missing between responsibilities 1 and 2, is the actual "handling" of the request. For a CRUD app, this could be where your DAOs come in.

DAOs

In my application, DAOs are Objects that extend a MongoDAO trait. The trait "knows" how to interact the the database drivers (get a connection), and supplies basic utilities. For instance:

/** Basic DAO Behavior for MongoDB documents. */
trait MongoDao {
  /** Name of the MongoDB Collection. */
  protected def colName: String

  /** Returns the default database. */
  protected def db = ReactiveMongoPlugin.db

  /** The actual collection used by the DAO. */
  protected def col: JSONCollection =
    db.collection[JSONCollection](colName)

  /** Returns the first result, if any, for the given query. */
  def findOne[DOC](q: JsObject)(implicit reads: Reads[DOC]) =
    col.find(q).cursor[DOC].headOption

  ...

I also have an abstract class that extends MongoDAO called, "AssetDAO". AssetDao provides 90% to 100% of the DAO implementation for 13 of the Models in my App. Here is it's type:

/** Common DAO behavior for all Assets. */
abstract class AssetDao[A <: AssetModel]
  (override val colName: String)(implicit format: Format[A])
extends MongoDao with ModulePermissions {

The DAO implementations can look like this:

object ArchetypeDao extends AssetDao[Archetype]("archetypes")

But most of them also have a couple methods that are all their own. Usually when I need to make use of Model specific properties in a query.

Model Companion Objects

Given the libraries that I'm using (notably play.api.lib.json), I find it best to put (de-)serialization code in the Companion Objects. For instance:

object Archetype {
  /** Converts Archetype to/from its MongoDB representation */
  implicit val fmt = Json.format[Archetype]

  /** Reads that are used for both Create and Update. */
  val coreReads = (
    (__ \ "name"   ).json.pickBranch(smlReqStr) and
    (__ \ "summary").json.pickBranch(medReqStr) and

    ((__ \ "description").json.pickBranch(fullText) or emptyObj)
  ) reduce
}

As you can see, the Companion Object contains all of the information it needs to (de-)serialize the case class to/from its MongoDB representation; and to/from its "client" representation. So the DAO ends up using the Json.format and the Controller uses the JSON Transformers.

Models

In this setup, all Models are simple case classes that represent persisted data. For instance:

case class Archetype (
  _id:         BSONObjectID,
  module_id:   BSONObjectID,
  doc_meta:    DocMeta,
  name:        String,
  summary:     String,
  description: Option[String]
) extends AssetModel

And here is the AssetModel trait that Archetype implements:

/** Describes some common behavior of asset types */
trait AssetModel {
  def _id:       BSONObjectID
  def module_id: BSONObjectID
  def doc_meta:  DocMeta
}

You may have noticed earlier that Archetype.coreReads doesn't reference all the fields the model has. That's because the "AssetControllers" are taking care of the rest of the fields for every AssetModel. Implementing AssetModel here is what makes that work.

To do it again, I might have the AssetModel Companion Objects extend a trait that provides the common field (de)serialization.

Conclusion

Hopefully, this answer has provided you with:

  1. A better understanding of using OO in Scala.
  2. Some ideas for organizing your Play App to write DRY code.
\$\endgroup\$
  • 3
    \$\begingroup\$ Now THAT's an answer! Awesome, keep it up! Don't feel pressured to post very long answers that cover everything though, posting a partial review is perfectly fine ;) \$\endgroup\$ – Mathieu Guindon Jan 31 '14 at 2:23
  • \$\begingroup\$ Github or Gist with examples? \$\endgroup\$ – Saad Malik Nov 12 '16 at 4:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.