Victor works on the Angular team at Google. He is interested in functional programming, the Web platform, and client-side applications. Being a language nerd he spends a lot of my time playing with Smalltalk, JS, Dart, Scala, Haskell, Clojure, Ruby, and Ioke. Victor is a DZone MVB and is not an employee of DZone and has posted 46 posts at DZone. You can read more from them at their website. View Full User Profile

DCI in Groovy

04.01.2011
| 9414 views |
  • submit to reddit

DCI (Data Context Interaction) is a new way to look at object oriented programming. If you’d like to read some theory to see the difference between DCI and traditional OOP there is a nice article coverting the topic:

And this presentation can be very helpful too:

In this post I’d like to show one simple example of a DCI program written in Groovy.

It isn’t easy to use DCI in Java as by its nature DCI requires sharing behavior between classes and Java doesn’t provide any decent ways to do it. But Groovy does - mixins.

Mixin is a very interesting approach allowing you to share a piece of behavior between several classes. The approach has been used by Smalltalk developers for more than 30 years but it started being popular in ‘more or less mainstream’ languages only a few years ago. To demonstrate how to use mixins in Groovy for implementing DCI I’ll use write a simple app:

Requirements:

  1. We have users
  2. We have companies
  3. Users can follow users
  4. Users can follow companies
  5. Users are entities stored in a database
  6. Companies are entities stored in a database

Basically, we have two domain classes: users and companies and usecases: when a user starts folllownig a company and he starts following another user.

Firstly, let’s create our domain classes:

class User {
int id
String name
int age
List followers
}

class Company {
int id
String name
String country
List followers
}

 

And a simple Database class representing persistent infrastructure of a real application:

class Database {
private users = [
1: new User(id: 1, name: 'Johh', age: 25, followers: []),
2: new User(id: 2, name: 'Piter', age: 25, followers: [])
]
private companies = [1: new Company(id: 1, name: 'BigCompany', country: 'Canada', followers: [])]

User findUserById(int id){
users[id]
}

Company findCompanyById(int id){
companies[id]
}

void updateUser(User user){
users[user.id] = user
}

void updateCompany(Company company){
companies[company.id] = company
}
}

 

Domain objects in DCI aren’t smart. They don’t provide methods for all possible use cases. They don’t interact with each other in complex ways. Instead, they have a set of fields and a bunch of convenient methods to access them.

All our business logic is concentrated in roles. Role is a piece of behavior that we can mix to our domain classes to solve business problems. We’ll need two roles for our toy application:

class Follower {
}

class Following {
void addFollower(follower){
followers << follower
}
}

 

Follower is a marker role. It isn’t necessary to create such a kind of a role but I like to do it as it clarifies my intent.

A bit of Groovy meta programming to make the syntax of adding a new role to a domain object better:

Object.metaClass.addRole = {role->
delegate.metaClass.mixin role
}


So, instead of:

object.metaClass.mixin RoleName

 

it will look like:

object.addRole RoleName

 

The only part that left is a context which will extract domain objects from the database, assign some roles to them and start a business use case:

class FollowersListContext {

Database db

void addFollowerToUser(int followingUserId, int followerUserId){
def following = db.findUserById(followingUserId)
def follower = db.findUserById(followerUserId)

following.addRole Following
follower.addRole Follower
following.addFollower follower

db.updateUser following
}

void addFollowerToCompany(int followingCompanyId, int followerUserId){
def following = db.findCompanyById(followingCompanyId)
def follower = db.findUserById(followerUserId)

following.addRole Following
follower.addRole Follower
following.addFollower follower

db.updateCompany following
}
}

Database db = new Database()
def context = new FollowersListContext(db: db)
context.addFollowerToUser(1, 2)

It may not be the most impressive example as we share only one line of code but it shows how all pieces work together. In a real word example roles will do much more than just adding an item to a collection. As a result this kind of decomposition will allow us to split complex behavior and avoid monster classes with thousands lines of code.

Having such a powerful language as Groovy it is not hard to adjust our code and make it look a bit better. For example, we can use such code for assigning roles:

def following = db.findCompanyById(followingCompanyId).inRole(Following)

 

Or even such code (though it looks a bit weird for me):

Following.playedBy(company).addFollower Follower.playedBy(user)

 

References
Published at DZone with permission of Victor Savkin, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Dierk Koenig replied on Sun, 2011/04/03 - 2:46pm

Thanks for the interesting post. I see an issue, though. You are mixing roles into meta classes. These classes seem not be per-instance meta classes - unless there is some code missing in the post. This will result in later mixing-in a role into an object of an already mixed class being ignored. It would be helpful to see the full code, including the test cases. cheers Dierk

Victor Savkin replied on Mon, 2011/04/04 - 6:50pm in response to: Dierk Koenig

Hi Dierk. Thank you for your feedback. You can look at the full code here: http://gist.github.com/902719. I think I am mixing roles into per instance meta classes (at least I was trying to do it) but probably I missed something. Please, take a look at the code and let me know if something should be fixed. Also, I'm waiting when reGINA is released. The first edition was awesome.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.