Skip to content

Commit

Permalink
Release 0.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
marcgrue committed Jan 8, 2014
1 parent 24381dc commit 3e911b5
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 45 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.idea
.idea_modules
demo/.idea
demo/.idea_modules
target
project/boot
*.swp
Expand Down
103 changes: 58 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# DCI in Scala
# Scala DCI

**A Scala macro annotation that allow us to assign objects to defined Roles
in a Context according to DCI.**
###Let runtime objects play Roles in a DCI Context!
_Stable version 0.4.1_

- Version 0.4.0
- Scala 2.10.3 (can easily be adapted to 2.11)
- Using macro-paradise plugin

The [Data, Context and Interaction (DCI)](http://en.wikipedia.org/wiki/Data,_context_and_interaction)
paradigm by Trygve Reenskaug and James Coplien embodies true object-orientation where
The [Data Context Interaction (DCI)](http://en.wikipedia.org/wiki/Data,_context_and_interaction) paradigm by Trygve Reenskaug and James Coplien embodies true object-orientation where
runtime Interactions between a network of objects in a particular Context
is understood _and_ coded as first class citizens.

Expand All @@ -19,16 +14,12 @@ case class Account(name: String, var balance: Int) {
def decreaseBalance(amount: Int) { balance -= amount }
}
```
This is what we in DCI call a "dumb" data class. It only "knows" about its own data and how
to manipulate that. The concept of a transfer between two accounts is outside of its
responsibilities and we delegate this to a Context - the MoneyTransfer context class.
In this way we can keep the Account class very slim and avoid that it gradually takes on
more and more responsibilities for each use case it participates in.

In a Money Transfer use case we can imagine a "Source" account where we take the money from
and a "Destination" account where we put the money. That could be our intuitive "Mental model"
of the transfer process. We code the Source and Destination concepts as Roles in the
Context:
This is what we in DCI sometimes call a "dumb" data class. It only "knows" about its own data and how to manipulate that.
The concept of a transfer between two accounts is outside its responsibilities and we delegate this to a Context - the MoneyTransfer context class. In this way we can keep the Account class very slim and avoid that it gradually takes on more and more responsibilities for each use case it participates in.

In a Money Transfer use case we can imagine a "Source" account where we take the money from and a "Destination" account where we put the money. That could be our "Mental Model" of a transfer. Interacting "concepts" of our model we call
"Roles" and now we can now code those Roles and Interactions directly in a DCI Context:

```Scala
@context
class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
Expand All @@ -49,10 +40,15 @@ class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
}
}
```
Our @context macro annotation transforms the abstract syntaxt tree (AST) of the context
class at compile time by prefixing role methods with role names and lifting those
methods into the context namespace. This is what we get after compilation has transformed our
MoneyTransfer context:

We want that source code to map as closely to our mental model as possible so that we can confidently and easily
overview and reason about _how the objects will interact at runtime_! We want to expect no surprises at runtime. With DCI we have all runtime interactions right there! No need to look through endless convoluted abstractions, tiers, polymorphism etc to answer the reasonable question _where is it actually happening, goddammit?!_

At compile time, our @context macro annotation transforms the abstract syntax tree (AST) of our code to enable our
_runtime data objects_ to "have" those extra Role Methods. Well, I'm half lying to you; the
objects won't "get new methods". Instead we call Role-name prefixed Role methods that are
lifted into Context scope which accomplishes what we intended in our source code:

```Scala
class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {

Expand All @@ -69,6 +65,7 @@ class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
}
```


## "Overriding" instance methods
In some cases we want to override the instance methods of a domain object with a Role method:
```Scala
Expand Down Expand Up @@ -196,8 +193,7 @@ role RoleName {
```

for defining a Role and its Role methods we need to make a Scala
contruct that is valid before our macro annotation can start transforming our code.
Maybe there's a way to achieve the above syntax with implicits but Dynamic does the job:
contruct that is valid before our macro annotation can start transforming our code:

```scala
object role extends Dynamic {
Expand Down Expand Up @@ -269,26 +265,39 @@ less DCI ideomatic since it looks less like a role definition than a method call
which is not the intention and result after source code transformation.


## Try it
## Scala DCI demo application

In the [Scala DCI Demo App](https://github.com/DCI/scaladci/tree/master/demo) you can see an example of how to create a DCI project.


## Using Scala DCI in your project

ScalaDCI is available for Scala 2.10.3 at [Sonatype](https://oss.sonatype.org/index.html#nexus-search;quick%7Eshapeless). To start coding with DCI in Scala add the following to your SBT build file:

libraryDependencies ++= Seq(
"com.marcgrue" %% "scaladci" % "0.4.1"
),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.0-M2" cross CrossVersion.full)


## Building Scala DCI
- Scala DCI is built with SBT 0.13.1.
- Latest release is version 0.4.1
- Ongoing development of 0.5.0-SNAPSHOT continues in the master branch
- Pull requests and comments are welcome! :-)

To build Scala DCI on your local machine:
```
git clone https://github.com/DCI/scaladci.git
cd scaladci
sbt
gen-idea // (if you use IntelliJ)
```

*NOTE: To use scaladci in your own project, you need to place it in a separate
sbt-project and let your own project depend on the scaladci project. This will
allow the macro transformation to take place in a separate compilation run.
Please have a look at the [build file of scaladci](
https://github.com/DCI/scaladci/blob/master/project/build.scala) to see how
this is set up.*
It's relatively easy to modify the code to run on Scala 2.11 too. I'll see if I can make some cross version...



Solution inspired by Risto Välimäki's
[post](https://groups.google.com/d/msg/object-composition/ulYGsCaJ0Mg/rF9wt1TV_MIJ)
and the
[Marvin](http://fulloo.info/Examples/Marvin/Introduction/)
DCI language by Rune Funch.

Have fun!

Expand All @@ -297,11 +306,15 @@ January 2014



#### Resources
DCI:
[Object-composition](https://groups.google.com/forum/?fromgroups#!forum/object-composition),
[Full-OO](http://fulloo.info),
[DCI wiki](http://en.wikipedia.org/wiki/Data,_Context,_and_Interaction)<br>
Scala:
[Macro annotations](http://docs.scala-lang.org/overviews/macros/annotations.html),
[Macro paradise](http://docs.scala-lang.org/overviews/macros/paradise.html)
### DCI resources
Discussions - [Object-composition](https://groups.google.com/forum/?fromgroups#!forum/object-composition)<br/>
Website - [Full-OO](http://fulloo.info)<br/>
Wiki - [DCI wiki](http://en.wikipedia.org/wiki/Data,_Context,_and_Interaction)

### Credits
Trygve Renskaug and James O. Coplien for inventing and developing DCI.

Scala DCI solution inspired by Risto
Välimäki's [post](https://groups.google.com/d/msg/object-composition/ulYGsCaJ0Mg/rF9wt1TV_MIJ) and
Rune Funch's [Marvin](http://fulloo.info/Examples/Marvin/Introduction/) DCI
language (now [Maroon](http://runefs.com/2013/02/14/using-moby-to-do-injectionless-dci-in-ruby/) for Ruby).
1 change: 1 addition & 0 deletions demo/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=0.13.1
19 changes: 19 additions & 0 deletions demo/project/build.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sbt._
import sbt.Keys._

object build extends Build {

lazy val demo = Project("demo", file("."), settings = buildSettings)

val buildSettings = Defaults.defaultSettings ++ Seq(
organization := "com.yourcompany",
version := "0.4.1",
scalaVersion := "2.10.3",
scalacOptions := Seq("-unchecked", "-deprecation"),
resolvers ++= Seq(Resolver.sonatypeRepo("releases")), //, Resolver.sonatypeRepo("snapshots")),
libraryDependencies ++= Seq(
"com.marcgrue" %% "scaladci" % "0.4.1"
),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.0-M2" cross CrossVersion.full)
)
}
66 changes: 66 additions & 0 deletions demo/src/main/scala/MoneyTransferApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import scaladci._

/*
Simple example of DCI in action - the money transfer example
1. Import scaladci._
2. Annotate a class with @context - that DCI-enables it.
3. Define roles names (like Actors in a use case)
4. Define role methods (what will those actors do...)
5. Define the trigger that starts off the chain of interactions between the roles
Normally we lowercase parameter names. "Source" and "Destination" of the MoneyTransfer
DCI context are an exception since we want to associate them with our role definitions.
A role definition has to match an object identifier in the context scope. Not necessarily
a class parameter, any val/var will do as long as the identifier name matches a defined role.
Compared to this school-book example, Data classes, DCI Contexts and the runtime
environment would of course normally be in separate tiers.
More info - http://github.com/dci/scaladci
Official website - http://fulloo.info
Discussions - https://groups.google.com/forum/#!forum/object-composition
*/

object MoneyTransferApp extends App {

// Data - knows nothing of transfers...
case class Account(name: String, var balance: Int) {
def increaseBalance(amount: Int) { balance += amount }
def decreaseBalance(amount: Int) { balance -= amount }
}

// DCI Context - encapsulates a specific "process"/"use case"/"network of interactions" etc
@context
class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {

Source.withdraw // Trigger method setting off the use case

role Source { // Role definition
def withdraw() { // Role method
Source.decreaseBalance(amount) // Instance method
Destination.deposit // Role interacts with other role
}
}

role Destination {
def deposit() {
Destination.increaseBalance(amount)
}
}
}


// Runtime environment

// Instantiate Data objects
val salary = Account("Salary", 3000)
val budget = Account("Budget", 1000)

// Instantiate Context with data objects and run use case
new MoneyTransfer(salary, budget, 700)

// Confirm that amount has transferred
assert(salary.balance == 3000 - 700, s"Salary balance should have been 3000 - 700 = 2300. Is now ${salary.balance}")
assert(budget.balance == 1000 + 700, s"Budget balance should have been 1000 + 700 = 1700. Is now ${budget.balance}")
}
83 changes: 83 additions & 0 deletions project/build.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import sbt._
import sbt.Keys._

object build extends Build with BuildSettings {

lazy val root = Project(
"root",
file("."),
settings = buildSettings ++ Seq(packagedArtifacts := Map.empty)
) aggregate(core, examples)

lazy val core = Project(
"scaladci",
file("core"),
settings = buildSettings
)

lazy val examples = Project(
"scaladci-examples",
file("examples"),
settings = buildSettings ++ Seq(
libraryDependencies ++= Seq(
"org.jscala" %% "jscala-macros" % "0.3",
"org.jscala" %% "jscala-annots" % "0.3"
),
packagedArtifacts := Map.empty
)
) dependsOn core
}

trait BuildSettings extends Publishing {

lazy val majorVersion = "0.4.1"
// lazy val versionFormat = "%s-SNAPSHOT"
lazy val versionFormat = "%s"

val buildSettings = Defaults.defaultSettings ++ publishSettings ++ Seq(
organization := "com.marcgrue",
version := versionFormat format majorVersion,
scalaVersion := "2.10.3",
scalacOptions := Seq("-unchecked", "-deprecation", "-feature"),
resolvers ++= Seq(Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots")),
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.specs2" %% "specs2" % "2.3.7" % "test"
),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.0-M2" cross CrossVersion.full)
)
}

trait Publishing {
val snapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
val releases = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/"

lazy val publishSettings = Seq(
publishMavenStyle := true,
publishTo <<= version((v: String) => Some(if (v.trim endsWith "SNAPSHOT") snapshots else releases)),
publishArtifact in Test := false,
pomIncludeRepository := (_ => false),
pomExtra := projectPomExtra
)

val projectPomExtra =
<url>https://github.com/dci/scaladci</url>
<licenses>
<license>
<name>Apache License</name>
<url>http://www.apache.org/licenses/</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>git@github.com:dci/scaladci.git</url>
<connection>scm:git:git@github.com:dci/scaladci.git</connection>
</scm>
<developers>
<developer>
<id>marcgrue</id>
<name>Marc Grue</name>
<url>http://marcgrue.com</url>
</developer>
</developers>
}

0 comments on commit 3e911b5

Please sign in to comment.