0
votes

I've been looking at several Grails sample apps, tutorials as well as books (Grails 2 QuickStart) trying to find out how business logic is implemented in Grails. Coming from a railsbackground I expect to see this stuff in the model, but all I'm seeing in the various examples is constraints and such.

Below is a prototype "Account" object with a balance and overdrawn property and a makeDeposit and makeWithdrawal method. It demonstrates all the sort of questions I have. (comments inline).

All of these things are not hard to do in Rails but since none of the books examples I've seen have these kind of methods in the Domain classes perhaps Domain Classes are not meant to have a lot of custom code in them. Would appreciate an example of how to properly implement something like this as a Grails Domain class (or however youre supposed to enfoce rules like these.

class Account {

List deposits
List withdrawals


public long balance = 0;
//is setting a default value the right way to initialize
// this value to 0 in the db?

private Date accountCreated;
//Does private modifer mean accountCreated won't get persisted to the db?
//Assuming I want it in the db, where do initialize this to a client
//supplied value, but make it write-once?  I gather I need to have 
//parameterless-constructor, so I can't supply it with 
//"new Account(creationDate) as you might do in Java.


public boolean isOverdrawn = false;
private void setIsOverdrawn() {}
//does the private setIsOverdrawn method below make this a readOnly to
//other objects in my application?

static hasMany = [deposits: Long, withdrawals: Long]
static constraints = {
    balance nullable: false
    accountCreated()  //if I do this, does that turn this from a
                    // field into a column?  
}

private void setBalance(long newBalance) {
    //will the private modifier prevent other objects
    //from setting balance directly?
    this.balance = newBalance;
    if (balance < 0) this.isOverdrawn = true else this.isOverdrawn = false;
}
//this should  be the only way for clients to change the balanace in the account.
public void makeDeposit(Long amount) {
    deposits.add(amount)
    setBalance(this.balance += amount);
    this.save()
}

public void makeWithdrawal(Long amount) {
    withdrawals.add(amount);
    setBalance(this.balance -= amount)
    this.save();

}

}

1

1 Answers

0
votes

Okay, so you have a larger question of "Where should I put my business logic?" and then also many other questions within your example domain class.

Let's start with the bigger question of "Where should I put my business logic?". There are two choices you can make here. First, as you have already demonstrated you can place the business logic inside the domain class itself. This isn't very common in Grails as the domain classes typically are more of an anemic domain class. In order to do this effectively you have to really understand how GORM and data binding works in order to accomplish quite a few things. As always reading the entire Grails documentation will help you understand how Grails expects things to be done.

The most common approach is to place all your business logic inside services in a service layer. These singleton classes are responsible for accessing and coridinating your domain classes to work as a cohesive system. Again, the Grails documentation on services covers this in theory. The documentation even states:

Services in Grails are the place to put the majority of the logic in your application...

Now you do have a few minor questions within your example. Overall you have a clear misunderstanding of how GORM and domain classes work within Grails. Public vs Private has no effect within a domain class. In order to make something read only you need to look at hooking into the hibernate events for beforeInsert and beforeUpdate and perhaps even transients. I recommend the GORM section of the Grails documentation for further information.

Since you have experience with Rails you need to try and stop comparing the two. Grails may share some things in common but it has it's own approach. Reading the documentation will help you spot those differences in approach. (Yes, I know it's a lot of reading but it's well written and has a lot of really good information.)