Skip to content
This repository has been archived by the owner on Dec 4, 2024. It is now read-only.

Commit

Permalink
Fixes #57 - adding MacWire with a train station example
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Weber committed Apr 14, 2016
1 parent ad2a300 commit 9c3d276
Show file tree
Hide file tree
Showing 18 changed files with 330 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package mesosphere.chaos.examples.trains

import akka.actor.{ActorSystem, Props}
import com.softwaremill.macwire.aop.ProxyingInterceptor
import com.softwaremill.macwire.wiredInModule
import com.softwaremill.tagging._
import mesosphere.chaos.examples.trains.modules.impl.loading.LoadListenerActor
import mesosphere.chaos.examples.trains.modules.impl.station.{Load, LoadListener}
import mesosphere.chaos.examples.trains.modules.{StationModule, ModernShuntingModule, TraditionalShuntingModule, LoadingModule}

object TrainStation extends App {
val system = ActorSystem("trainSystem")

val traditionalModules = new TraditionalShuntingModule
with LoadingModule
with StationModule {

lazy val logEvents = ProxyingInterceptor { ctx =>
println(s"${ctx.target} calling method: ${ctx.method.getName}")
ctx.proceed()
}
lazy val actorSystem = system
}

val modernModules = new ModernShuntingModule
with LoadingModule
with StationModule {
lazy val logEvents = ProxyingInterceptor { ctx =>
println(s"${ctx.target} calling method: ${ctx.method.getName}")
ctx.proceed()
}
lazy val actorSystem = system
}

println("# Traditional station:")
println(traditionalModules.trainStation("Old school station.").prepareAndDispatchNextTrain())
println("\n# Modern station:")
println(modernModules.trainStation("Futuristic teleporting station.").prepareAndDispatchNextTrain())


// Dynamically wired in PlugIns
val wired = wiredInModule(modernModules)

val pluginList = Seq(classOf[SlackPlugin])

val plugins = pluginList.map { pluginClass =>
wired
.wireClassInstance(pluginClass)
.asInstanceOf[TrainStationPlugin]
}

plugins.foreach(_.init())


// Actor injection

// usage; statically checked ActorRef types!
val loadListener = system
.actorOf(Props[LoadListenerActor])
.taggedWith[LoadListener]

val reactiveTrainDispatch = modernModules.createReactiveTrainDispatch(loadListener)

reactiveTrainDispatch ! Load
}

trait TrainStationPlugin {
def init(): Unit
}

class SlackPlugin extends TrainStationPlugin {
override def init() = { println("Slack plugin initialized.") }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mesosphere.chaos.examples.trains.modules

import com.softwaremill.macwire.aop.Interceptor

trait InterceptorLogging {
/**
* Using interceptors is a two-step process.
* First, we have to declare what should be intercepted.
* Ideally, this shouldn’t involve the implementation of the interceptor in any way.
* Secondly, we have to define what the interceptor does - the behaviour.
*/
def logEvents: Interceptor
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mesosphere.chaos.examples.trains.modules

import com.softwaremill.macwire._
import mesosphere.chaos.examples.trains.modules.impl.loading.{CarLoader, CarType, CraneController, TrainLoader}
import mesosphere.chaos.examples.trains.modules.impl.shunting.PointSwitcher

@Module
trait LoadingModule extends InterceptorLogging {
lazy val craneController = logEvents(wire[CraneController])
lazy val trainLoader = logEvents(wire[TrainLoader])

// Factories as functions
lazy val carLoaderFactory = (ct: CarType) => wire[CarLoader]

// dependency of the module
def pointSwitcher: PointSwitcher
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mesosphere.chaos.examples.trains.modules

import com.softwaremill.macwire._
import mesosphere.chaos.examples.trains.modules.impl.shunting._

@Module
trait ShuntingModule extends InterceptorLogging {
lazy val pointSwitcher = logEvents(wire[PointSwitcher])

// dependency of the module
def trainShunter: TrainShunter
}

trait TraditionalShuntingModule extends ShuntingModule {
lazy val trainCarCoupler = logEvents(wire[TrainCarCoupler])
lazy val trainShunter = logEvents(wire[TraditionalTrainShunter])
}

trait ModernShuntingModule extends ShuntingModule {
lazy val trainShunter = logEvents(wire[TeleportingTrainShunter])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package mesosphere.chaos.examples.trains.modules

import akka.actor.{ActorSystem, ActorRef, Props}
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import mesosphere.chaos.examples.trains.modules.impl.loading.TrainLoader
import mesosphere.chaos.examples.trains.modules.impl.station._

@Module
trait StationModule extends ShuntingModule with LoadingModule {
// Factory function
def trainStation(name: String) = logEvents(wire[TrainStation])

// Multiple instances via tagging
lazy val regularTrainLoader = wire[TrainLoader].taggedWith[Regular]
lazy val liquidTrainLoader = wire[TrainLoader].taggedWith[Liquid]

lazy val trainDispatch = logEvents(wire[TrainDispatch])

// Actor wiring

// actor system module dependency
def actorSystem: ActorSystem

def createReactiveTrainDispatch(loadListener: ActorRef @@ LoadListener) =
actorSystem.actorOf(Props(wire[ReactiveTrainDispatch]))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mesosphere.chaos.examples.trains.modules

import com.softwaremill.macwire._
import mesosphere.chaos.examples.trains.modules.impl.stats.{LoadingStats, ShuntingStats}

class StatsModule(shuntingModule: ShuntingModule, loadingModule: LoadingModule) {
lazy val loadingStats = wire[LoadingStats]
lazy val shuntingStats = wire[ShuntingStats]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package mesosphere.chaos.examples.trains.modules.impl.loading

class CraneController() {
def controll() = println("crane moved")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mesosphere.chaos.examples.trains.modules.impl.loading

import akka.actor.Actor
import mesosphere.chaos.examples.trains.modules.impl.station.Load

case object Full

class LoadListenerActor extends Actor {
override def receive: Receive = {
case Load => sender() ! Full
case _ => println("unknown message type")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mesosphere.chaos.examples.trains.modules.impl.loading

import mesosphere.chaos.examples.trains.modules.impl.loading.TrainLoader.CarLoaderFactory
import mesosphere.chaos.examples.trains.modules.impl.shunting.PointSwitcher

sealed trait CarType
trait Coal extends CarType
trait Refrigerated extends CarType

class CarLoader()

class TrainLoader(craneController: CraneController,
pointSwitcher: PointSwitcher,
carLoaderFactory: CarLoaderFactory) {
def load() = {
println("loaded")
pointSwitcher.switch()
}
}

object TrainLoader {
type CarLoaderFactory = CarType => CarLoader
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package mesosphere.chaos.examples.trains.modules.impl.shunting

class PointSwitcher() {
def switch() = println("switched")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package mesosphere.chaos.examples.trains.modules.impl.shunting

class TrainCarCoupler() { def couple() = println("coupled") }
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mesosphere.chaos.examples.trains.modules.impl.shunting

trait TrainShunter { def shunt() = println("shunted") }
class TraditionalTrainShunter(pointSwitcher: PointSwitcher,
trainCarCoupler: TrainCarCoupler) extends TrainShunter {
override def shunt() = {
pointSwitcher.switch()
trainCarCoupler.couple()
super.shunt()
}
}
class TeleportingTrainShunter() extends TrainShunter {
override def shunt() = {
println("teleported")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package mesosphere.chaos.examples.trains.modules.impl.station

import com.softwaremill.tagging.@@
import akka.actor.{ActorRef, Actor}
import mesosphere.chaos.examples.trains.modules.impl.loading.{Full, TrainLoader}

case object Load
trait LoadListener

class ReactiveTrainDispatch(
trainLoader: TrainLoader @@ Regular,
trainDispatch: TrainDispatch,
loadListener: ActorRef @@ LoadListener) extends Actor {
def receive = {
case Load => loadListener ! Load
case Full => println("reactively fully loaded")
case _ => println("unknown message type")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package mesosphere.chaos.examples.trains.modules.impl.station

class TrainDispatch() { def dispatch() = "dispatched" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mesosphere.chaos.examples.trains.modules.impl.station

import com.softwaremill.tagging.@@
import mesosphere.chaos.examples.trains.modules.impl.loading.TrainLoader
import mesosphere.chaos.examples.trains.modules.impl.shunting.TrainShunter

trait Regular
trait Liquid

class TrainStation(name: String,
trainShunter: TrainShunter,
regularTrainLoader: TrainLoader @@ Regular,
liquidTrainLoader: TrainLoader @@ Liquid,
trainDispatch: TrainDispatch) {

def prepareAndDispatchNextTrain() = {
trainShunter.shunt()
regularTrainLoader.load()
liquidTrainLoader.load()
trainDispatch.dispatch()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mesosphere.chaos.examples.trains.modules.impl.stats

import mesosphere.chaos.examples.trains.modules.impl.loading.TrainLoader
import mesosphere.chaos.examples.trains.modules.impl.shunting.TrainShunter

class LoadingStats(trainLoader: TrainLoader)
class ShuntingStats(trainShunter: TrainShunter)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mesosphere.chaos.examples.trains.modules

import com.softwaremill.macwire.aop.NoOpInterceptor
import mesosphere.chaos.examples.trains.modules.impl.shunting.PointSwitcher
import org.mockito.Mockito._
import org.scalatest.FlatSpec

class ShuntingModuleTest extends FlatSpec {
it should "work" in {
// given
val mockPointSwitcher = mock(classOf[PointSwitcher])

// when
val moduleToTest = new TraditionalShuntingModule {
// the mock implementation will be used to wire the graph
override lazy val pointSwitcher = mockPointSwitcher
lazy val logEvents = NoOpInterceptor
}
moduleToTest.trainShunter.shunt()

// then
verify(mockPointSwitcher).switch()
}
}
31 changes: 30 additions & 1 deletion project/build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ object ChaosBuild extends Build {
base = file("chaos-examples"),
settings = baseSettings ++
formatSettings ++
publishSettings
publishSettings ++
Seq(
libraryDependencies ++= Dependencies.examples
)
).dependsOn(root % "compile->compile; test->test")

lazy val root = Project(
Expand Down Expand Up @@ -119,10 +122,28 @@ object Dependencies {
jclOverSlf4j % "compile",
log4jOverSlf4j % "compile",

// The macros subproject contains only code which is used at compile-time, hence the provided scope.
macWireMacros % "provided",
// The util subproject contains tagging, Wired and the @Module annotation; if you don't use these features, you don't need to include this dependency.
macWireUtil % "compile",
macWireProxy % "compile",

// test
Test.junit % "test",
Test.mockito % "test"
)

val examples = Seq(
akkaActor % "compile",
// The macros subproject contains only code which is used at compile-time, hence the provided scope.
macWireMacros % "provided",
// The util subproject contains tagging, Wired and the @Module annotation; if you don't use these features, you don't need to include this dependency.
macWireUtil % "compile",
macWireProxy % "compile",

// test
Test.mockito % "test"
)
}

object Dependency {
Expand All @@ -141,10 +162,12 @@ object Dependency {
val Slf4j = "1.7.12"
val LiftMarkdown = "2.6.2"
val Glassfish = "2.2.6"
val MacWire = "2.2.2"

// test deps versions
val JUnit = "4.12"
val Mockito = "1.10.19"
val Akka = "2.3.9"
}

val guava = "com.google.guava" % "guava" % V.Guava
Expand Down Expand Up @@ -176,6 +199,12 @@ object Dependency {
val julToSlf4j = "org.slf4j" % "jul-to-slf4j" % V.Slf4j
val jclOverSlf4j = "org.slf4j" % "jcl-over-slf4j" % V.Slf4j

val macWireMacros = "com.softwaremill.macwire" %% "macros" % V.MacWire
val macWireUtil = "com.softwaremill.macwire" %% "util" % V.MacWire
val macWireProxy = "com.softwaremill.macwire" %% "proxy" % V.MacWire

val akkaActor = "com.typesafe.akka" %% "akka-actor" % V.Akka

object Test {
val junit = "junit" % "junit" % V.JUnit
val mockito = "org.mockito" % "mockito-all" % V.Mockito
Expand Down

0 comments on commit 9c3d276

Please sign in to comment.