Java/Scala Lab 2016. Григорий Кравцов: Реализация и тестирование DAO слоя с нативным DSL с помощью неявных классов на примере взаимодействия с MongoDB через ReactiveMongo драйвер.
The document discusses using the DAO pattern with implicit classes in Scala to provide a separation between low-level data access and high-level business services. It describes implementing a DAO layer with a native Scala DSL using implicit classes, and testing the DAO layer by embedding MongoDB for integration tests. Advantages of the DAO approach include easy code reading and support, while disadvantages include potential difficulty with complex repositories and implicit conversions.
AI&BigData Lab 2016. Александр Баев: Transfer learning - зачем, как и где.
Ähnlich wie Java/Scala Lab 2016. Григорий Кравцов: Реализация и тестирование DAO слоя с нативным DSL с помощью неявных классов на примере взаимодействия с MongoDB через ReactiveMongo драйвер.
Introduction to Apache Tajo: Data Warehouse for Big DataGruter
Ähnlich wie Java/Scala Lab 2016. Григорий Кравцов: Реализация и тестирование DAO слоя с нативным DSL с помощью неявных классов на примере взаимодействия с MongoDB через ReactiveMongo драйвер. (20)
Java/Scala Lab 2016. Григорий Кравцов: Реализация и тестирование DAO слоя с нативным DSL с помощью неявных классов на примере взаимодействия с MongoDB через ReactiveMongo драйвер.
1. Implementation and testing DAO layer
with native DSL with using
the implicit classes mechanism in Scala
Speaker:
Hryhoriy Kravtsov, PhD.,
R&D Chief Officer,
VertaMedia LLC
2. DAO Pattern
Data Access Object Pattern [1] or DAO pattern is used to separate low level
data accessing API or operations from high level business services. Following
are the participants in Data Access Object Pattern.
Data Access Object Interface - This interface defines the standard operations
to be performed on a model object(s).
Data Access Object concrete class - This class implements above interface.
This class is responsible to get data from a data source which can be
database / xml or any other storage mechanism.
Model Object or Value Object - This object is simple POJO containing get/set
methods to store data retrieved using DAO class.
3. Repository Pattern
A Repository [2] mediates between the domain and data mapping
layers, acting like an in-memory domain object collection. Client
objects construct query specifications declaratively and submit them to
Repository for satisfaction. Objects can be added to and removed from
the Repository, as they can from a simple collection of objects, and the
mapping code encapsulated by the Repository will carry out the
appropriate operations behind the scenes.
Conceptually, a Repository encapsulates the set of objects persisted
in a data store and the operations performed over them, providing a
more object-oriented view of the persistence layer. Repository also
supports the objective of achieving a clean separation and one-way
dependency between the domain and data mapping layers.
4. Repository vs DAO
DAO pattern [3] offers only a loosely defined contract. It suffers from
getting potential misused and bloated implementations.
The repository pattern uses a metaphor of a Collection. This metaphor
gives the pattern a tight contract and make it easier to understand by your
fellow colleagues.
Repository and DAO [4], in conclusion, have similar intentions only that the
Repository is a higher level concept dealing directly with business/domain
objects, while DAO is more lower level, closer to the database/storage
dealing only with data. A (micro)ORM is a DAO that is used by a Repository.
For data-centric apps, a repository and DAO are interchangeable
because the ‘business’ objects are simple data.
5. Implicit classes
Scala 2.10 [5] introduced a new feature called implicit classes.
An implicit class is a class marked with the implicit keyword.
This keyword makes the class’ primary constructor available for
implicit conversions when the class is in scope.
object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = {
def loop(current: Int): Unit =
if(current > 0) {
f
loop(current - 1)
}
loop(x)
}
}
}
scala> import Helpers._
import Helpers._
scala> 5 times println("HI")
HI
HI
HI
HI
HI
6. Restrictions
1. They must be defined inside of another
trait/class/object.
object Helpers {
implicit class RichInt(x: Int) // OK!
}
implicit class RichDouble(x: Double) // BAD!
2. They may only take one non-implicit argument in their constructor.
implicit class RichDate(date: java.util.Date) // OK!
implicit class Indexer[T](collecton: Seq[T], index: Int) // BAD!
implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // OK!
While it’s possible to create an implicit class with more than one
non-implicit argument, such classes aren’t used during implicit lookup.
3. There may not be any method,
member or object in scope with the same
name as the implicit class.
Note: This means an implicit class cannot
be a case class.
object Bar
implicit class Bar(x: Int) // BAD!
val x = 5
implicit class x(y: Int) // BAD!
implicit case class Baz(x: Int) // BAD!
8. DAO in Action
class AnalyzerActor(db: DefaultDB) extends NamedLoggingActor {
import scala.concurrent.ExecutionContext.Implicits.global
import com.vertamedia.<>.dao._
private def replaceSample(sample: Sample): Future[Boolean] = {
logger.debug("Replace old sample with new one.")
for {
diffRemoveResult <- db.diffSample.removeByPartnerNameAndUrl(sample.conf.name, sample.conf.url)
sampleRemoveResult <- db.sample.removeByPartnerNameAndUrl(sample.conf.name, sample.conf.url) if diffRemoveResult
insertResult <- db.sample.insert(sample) if sampleRemoveResult
} yield {
insertResult
}
}
override def receive: Receive = {
case sample: Sample =>
...
}
override val actorName: String = Actors.Analyzer.name
}
The Legend:
Code with using DAO pattern
Code with using
Repository pattern
Please, compare with info from the official site of MongoDB:
9. Embedded MongoDB
import de.flapdoodle.embed.mongo._
trait EmbeddedMongoTestFixture extends FunSpec with BeforeAndAfterAll {
private val host = MongoConnection.DefaultHost
private val version = Version.V3_0_5
private[this] lazy val port = { val socket = new ServerSocket(0); val port = socket.getLocalPort
socket.close(); port }
private[this] lazy val net = new Net(host, port, Network.localhostIsIPv6())
private[this] lazy val mongod: MongodProcess = {
val runtime = MongodStarter.getDefaultInstance
val mongodConfig = new MongodConfigBuilder().version(version).net(net).build()
runtime.prepare(mongodConfig).start()
}
private[this] lazy val _db = mongoDB(host, port, s"test-${UUID.randomUUID()}")
def mongoDB(host: String, port: Int, name: String): DefaultDB = {
import scala.concurrent.ExecutionContext.Implicits.global
val driver = new MongoDriver(); val connection: MongoConnection = driver.connection(List(s"$host:$port"))
connection(name)
}
def startMongo(): Unit = { val _mongod = mongod; db }
override def beforeAll() = { super.beforeAll(); startMongo() }
def shutDownMongo(): Unit = { _db.connection.close(); mongod.stop() }
override def afterAll() = { shutDownMongo(); super.afterAll()}
def db: DefaultDB = _db
}
10. DAO Testing in Action
class MongoDaoTest extends EmbeddedMongoTestFixture with BeforeAndAfterEach {
val timeOut = 5 seconds
import scala.concurrent.ExecutionContext.Implicits.global
val config = AppConfiguration(
analyzerPoolSize = 3, vastHttpTimeOut = Interval(5, "seconds"), objectHttpTimeOut = Interval(7, "seconds")
)
override def beforeEach() = {
...
Await.result(db.config.insert(config), timeOut)
...
}
override def afterEach() = {
Await.result( for {
_ <- db.partners.dropCollection()
...
} yield (), timeOut)
}
describe("Config DAO should") {
it("return exist config") {
val actual = Await.result(db.config.getConfig, timeOut)
assert(actual === config)
}
....
}
}
The Legend:
Code with using DAO pattern
Code with using
Repository pattern
11. Advantages and
Disadvantages
YES!
- easy code reading for DAO/compact Repository
- easy supporting for DAO/compact Repository
- familiar notation with MongoShell
- easy to test
NO!
- difficult reading of “reach” Repository
- difficult supporting “reach” Repository
- hate to the implicit conversation
To use or not to use?
For your decision!