user3914455
user3914455

Reputation: 257

Grails commandObject - design best practices

I apologize at the outset as this is more of a design question rather than a specific problem so it may not have a simple answer .. Anyway I am developing quite a complex piece of code to process transactions in a warehouse system. The transactions themselves are user defined with switches which determine fields to enter on a screen .. I use a command object to process / validate the form. Many of the validation steps are straightforward but some are a little tricky and have cross dependencies between the screen fields .. So for instance I may prompt for the user to enter a serial reference but if that doesn't uniquely identify a carton I need to ask for a part to go with it .. I then hit the database to validate these combinations .. The screen may also have a document reference for the user to enter .. This in turn may be cross referenced with the part / serial on the screen (by hitting the database) to ensure that the part/serial is for the document .. This continues, depending on what's been defined on the transaction, with more cross references and validations. What I end up with is a lot of 'if this entered and this entered .. validate .. else .. validate' type stuff in the command object and it looks really ugly .. So my questions are : Should I be putting ALL my validation in the command Object (all the database checking etc) or is there somewhere better to put it and is there anything I can do better than all my complicated if/else combos given that this is a command object ? I had thought of creating additional classes which the current command object could kinda spawn , making those validatable and spreading the logic out amongst those .. If anyone would like to chip in i'd appreciate the discussion ..

After Joshua's comments i've started refactoring the code into a service .. It's not complete but it's taking shape ..

@Transactional
class TransactionValidationService {

static enum state {
    RECEIPT,
    RECEIPT_SERIAL,
    RECEIPT_DOCUMENT,
    RECEIPT_PART,
    RECEIPT_SERIAL_PART,
    RECEIPT_SERIAL_DOCUMENT,
    RECEIPT_PART_DOCUMENT,
    ISSUE,
    TRANSFER
}

def validateTransaction(TransactionDetailCommand transaction) {

    // Set initial state  ..

    def currentState

    switch (transaction.transactionType.processType) {

        case ProcessType.ISSUE:
            currentState = state.ISSUE
            break

        case ProcessType.RECEIPT:
            currentState = state.RECEIPT
            break

        case ProcessType.TRANSFER:
            currentState = state.TRANSFER

    }

    switch (currentState) {

        case state.RECEIPT:

            if (transaction.serialReference) {
                // validateSerialReference
                currentState = state.RECEIPT_SERIAL
            } else if (transaction.documentHeader) {
                // validateReceiptDocument
                currentState = state.RECEIPT_DOCUMENT
            }

            break

        case state.RECEIPT_SERIAL:

            if (transaction.part) {
                // validatePartSerial
                currentState = state.RECEIPT_SERIAL_PART
            }

            if (transaction.documentHeader) {
                // validateDocumentPart
                currentState = state.RECEIPT_SERIAL_DOCUMENT
            }

            break

        case state.ISSUE:
            break

        case state.TRANSFER:
            break

    }

}

}

Upvotes: 0

Views: 181

Answers (1)

Joshua Moore
Joshua Moore

Reputation: 24776

First off, using command objects to gather this information is the correct choice.

However, implementing complex validation logic within the constraints as custom validators can be a bit overwhelming. You may want to consider injecting a service into your command object then delegating the validation to the service from within the custom validator.

For example

@Validateable
@ToString(includeNames=true)
class MyExampleCommand {
  def myValidationService = Holders.grailsApplication.mainContext.myValidationService

  String someThing
  Long someValue
  ..
  static constraints = {
    someThing(
      nullable: false, 
      blank: false, 
      size:1..20, 
      validator: { val, obj -> 
        obj.myValidationService.validateSomeThing(obj) 
      }
    )
    ...
  }
  ...
}

Upvotes: 1

Related Questions