2015-01-30 16 views
9

Xem xét dịch vụ sau (giao dịch theo mặc định). Người chơi phải luôn có một tài khoản. Một trình phát không có ít nhất một tài khoản tương ứng là trạng thái lỗi.Grails 2.4.4: Cách khôi phục đáng tin cậy theo phương thức dịch vụ phức tạp

class playerService { 
    def createPlayer() { 
     Player p new Player(name: "Stephen King") 
     if (!p.save()) { 
     return [code: -1, errors:p.errors] 
     } 
     Account a = new Account(type: "cash") 
     if (!a.save()) { 
     // rollback p ! 
     return [code: -2, errors:a.errors] 
     } 
     // commit only now! 
     return [code: 0, player:p] 
    } 
} 

Tôi đã thấy mô hình này bằng cách phát triển grails có kinh nghiệm, và khi tôi nói với họ rằng nếu tạo ra các tài khoản của người chơi thất bại vì lý do nào, nó sẽ không rollback các cầu thủ, và sẽ rời khỏi DB trong một không hợp lệ nhà nước, họ nhìn tôi như thể tôi đang phát điên vì những cây grails xử lý lăn ngược lại người chơi bởi vì các dịch vụ được giao dịch đúng không?

Vì vậy, sau đó, là một anh chàng SQL, tôi tìm cách gọi rollback trong grails. Không có một. Theo các bài đăng khác nhau, chỉ có 2 cách để buộc grails khôi phục trong một dịch vụ:

  1. ném một ngoại lệ không được chọn. Bạn biết điều này đúng không?
  2. không sử dụng phương pháp dịch vụ hoặc chú thích giao dịch, sử dụng cấu trúc này:

.

DomainObject.withTransaction {status -> 
    //stuff 
    if (someError) { 
     status.setRollbackOnly() 
    } 
} 

1. ném một ngoại lệ được kiểm soát


1,1 Vì vậy, chúng ta phải ném ngoại lệ thời gian chạy để rollback. Điều này là ok cho tôi (tôi thích trường hợp ngoại lệ), nhưng điều này sẽ không với các nhà phát triển grails chúng tôi có những người xem ngoại lệ như là một sự trở lại với Java và là uncool. Nó cũng có nghĩa là chúng ta phải thay đổi toàn bộ cách ứng dụng hiện đang sử dụng lớp dịch vụ của nó.

1.2 Nếu ngoại lệ được ném, bạn sẽ mất p.errors - bạn mất chi tiết xác thực.

1.3 Các nhà phát triển grails mới của chúng tôi không biết sự khác biệt giữa một ngoại lệ không được kiểm tra và kiểm tra, và không biết cách nói sự khác biệt. Điều này thực sự nguy hiểm.

1.4. sử dụng .save (failOnError: true) Tôi là một fan hâm mộ lớn của việc sử dụng này, nhưng nó không thích hợp ở khắp mọi nơi. Đôi khi bạn cần phải kiểm tra lý do trước khi đi xa hơn, không ném một ngoại lệ. Các ngoại lệ mà nó có thể tạo luôn được kiểm tra, luôn được bỏ chọn hay không? I E. sẽ failOnError AWLAYS rollback, bất kể nguyên nhân là gì? Không ai tôi đã hỏi biết câu trả lời cho điều này, điều này thật đáng lo ngại, họ đang sử dụng đức tin mù quáng để tránh các DB bị hỏng/không nhất quán.

1.5 Điều gì sẽ xảy ra nếu bộ điều khiển gọi dịch vụ A, gọi Dịch vụ B, sau đó là dịch vụ C. Dịch vụ A phải bắt giữ ngoại lệ và trả về giá trị trả về được định dạng độc đáo cho bộ điều khiển. Nếu Service C ném một ngoại lệ, được bắt bởi Service A, sẽ dịch vụ các giao dịch Bs được khôi phục? Điều này là rất quan trọng để biết để có thể xây dựng một ứng dụng làm việc.

CẬP NHẬT 1: Sau khi thực hiện một số kiểm tra, có vẻ như bất kỳ ngoại lệ thời gian chạy nào, ngay cả khi bị ném và bị bắt trong một số cuộc gọi con không liên quan, sẽ khiến mọi thứ trong phụ huynh quay trở lại. Tuy nhiên, không dễ để biết trong phiên cha mẹ rằng việc khôi phục này đã xảy ra - bạn cần đảm bảo rằng nếu bạn bắt bất kỳ ngoại lệ nào, bạn có thể trả lại hoặc chuyển một số thông báo lại cho người gọi để cho biết rằng nó đã bị lỗi một cách mà mọi thứ khác sẽ được khôi phục.

2.withTransaction


2.1 Đây có vẻ là một cấu trúc chợ. Làm cách nào để gọi điều này và tôi sẽ chuyển cho thông số "trạng thái" nào? "SetRollbackOnly" là gì chính xác là. Tại sao nó không chỉ được gọi là "rollback". Phần "Chỉ" là gì? Nó được gắn với một đối tượng miền, khi phương thức của bạn có thể muốn cập nhật một số đối tượng miền khác nhau.

2.2 Bạn phải đặt mã này ở đâu? Trong lớp DomainObject? Trong thư mục nguồn (tức là không có trong một dịch vụ hoặc bộ điều khiển?)? Trực tiếp trong bộ điều khiển? (chúng tôi không muốn sao chép logic nghiệp vụ trong bộ điều khiển)

3. Tình huống lý tưởng.


3,1 Các trường hợp chung là chúng tôi muốn tất cả mọi thứ chúng ta làm trong một phương pháp dịch vụ để quay trở lại nếu bất cứ điều gì ở chỗ phương pháp dịch vụ không thể được lưu lại cho lý do nào, hoặc ném bất kỳ ngoại lệ cho bất kỳ lý do (kiểm tra hoặc không được kiểm soát).

3.2 Lý tưởng nhất là tôi muốn các phương thức dịch vụ "luôn quay trở lại, trừ khi tôi gọi cam kết một cách rõ ràng", đây là chiến lược an toàn nhất, nhưng điều này là không thể.

Câu hỏi đặt ra là làm cách nào để đạt được tình huống lý tưởng?

Sẽ gọi lưu (failOnError: true) LUÔN khôi phục mọi thứ, bất kể lý do không thành công là gì? Điều này là không hoàn hảo, vì nó không phải là dễ dàng cho người gọi để biết được đối tượng miền tiết kiệm gây ra vấn đề.

Hoặc người ta có định nghĩa rất nhiều lớp ngoại lệ phân lớp runtimeException, sau đó khai thác rõ ràng từng bộ điều khiển trong bộ điều khiển để tạo phản hồi thích hợp không? Đây là cách Java cũ, và groovy của chúng tôi đã phát triển cách tiếp cận này do số lượng mã đĩa nồi hơi chúng ta sẽ phải viết.

Phương pháp nào mọi người sử dụng để đạt được điều này?

+0

đây là một câu hỏi đối với tôi cũng vậy, tôi quyết định bắt tất cả mọi thứ trong điều khiển và ném lỗi của riêng tôi mà là con của RuntimeException. Tôi thấy đây là cách duy nhất để tạo ra phản ứng thích hợp. –

Trả lời

3

Tôi sẽ không tự gọi mình là chuyên gia và câu hỏi này đã hơn một tuổi, nhưng tôi có thể trả lời một số câu hỏi này, nếu chỉ cho những người tìm kiếm trong tương lai. Tôi vừa mới tái cấu trúc một số bộ điều khiển để sử dụng các dịch vụ để tận dụng các giao dịch.

I have seen this pattern by experienced grails developers, and when I tell them that if creation of the account of the player fails for any reason, it wont rollback the player, and will leave the DB in an invalid state, they look at me like I am mad because grails handles rolling back the player because services are transactional right?

Tôi không nhìn thấy trong tài liệu mà nó một cách rõ ràng rằng trở về từ một phương pháp dịch vụ làm không rollback giao dịch, nhưng tôi không thể tưởng tượng rằng đây sẽ là một hành vi rất lành mạnh. Tuy nhiên, thử nghiệm là một cách dễ dàng để chứng minh bản thân.

1.2 If an exception is thrown, you lose the p.errors - you lose the validation detail.

Vì bạn là người ném ngoại lệ, bạn có thể ném lỗi cùng với nó. Ví dụ:

// in service 
    if (!email.save()) { 
     throw new ValidationException("Couldn't save email ${params.id}", email.errors) 
    } 

Khi bạn bắt ngoại lệ, bạn tải lại các trường hợp (vì ném một ngoại lệ xóa phiên), đưa các lỗi trở lại ví dụ, và sau đó vượt qua đó để quan điểm như bình thường:

// in controller 
    } catch (ValidationException e) { 
     def email = Email.read(id) 
     email.errors = e.errors 
     render view: "edit", model: [emailInstance: email] 
    } 

Điều này được thảo luận trong tiêu đề "Lỗi xác thực và khôi phục", xuống trang từ http://grails.github.io/grails-doc/2.4.4/guide/single.html#transactionsRollbackAndTheSession.

1.4. use .save(failOnError: true) I am a big fan of using this, but its not appropriate everywhere. Sometimes you need to check the reason before going further, not throw an exception. Are the exceptions it can generate always checked, always unchecked, or either? I.e. will failOnError AWLAYS rollback, no matter what the cause? No one I have asked knows the answer to this, which is disturbing, they are using blind faith to avoid corrupted/inconsistent DBs.

failOnError will cause save() to throw a ValidationException, vì vậy có, nếu bạn đang ở trong một giao dịch và không kiểm tra ngoại lệ đó, giao dịch sẽ được khôi phục.

Nói chung, có vẻ như không phải là "Grailsy" để sử dụng failOnError rất nhiều, có thể vì lý do bạn liệt kê (ví dụ: thiếu kiểm soát). Thay vào đó, bạn kiểm tra xem save() có thất bại hay không (if (!save()) ...) và thực hiện hành động dựa trên điều đó.

  1. withTransaction

Tôi không chắc chắn về điểm này, vì SpringSource thực sự khuyến khích sử dụng dịch vụ cho mọi thứ. Cá nhân tôi cũng không thích nó.

Nếu bạn muốn thực hiện một dịch vụ cụ thể không giao dịch, sau đó thực hiện một phương thức giao dịch, bạn có thể chỉ chú thích phương thức đó với @Transactional (trừ khi nhà phát triển của bạn cũng không thích chú thích vì chúng quá "Java";)).

Lưu ý! Ngay sau khi bạn đánh dấu một phương thức với @Transactional, dịch vụ tổng thể sẽ trở thành không giao dịch.

3.1 The general case is we want every thing we do in a service method to roll back if anything in that service method cant be saved for any reason, or throws any exception for any reason (checked or unchecked).

Tôi cảm thấy như ngoại lệ được kiểm tra thường được coi là không "Groovy" (cũng khiến chúng không phải là Grails-y). Không chắc chắn về lý do cho điều đó.

Tuy nhiên, có vẻ như bạn có thể yêu cầu dịch vụ của bạn khôi phục ngoại lệ đã kiểm tra, bằng cách liệt kê chúng trong rollbackFor option to @Transactional.

Or do people define lots of exception classes which subclass runtimeException, then explicit catch each of them in the controller to create the appropriate response? This is the old Java way, and our groovy devs pooh pooh this approach due to the amount of boiler plate code we will have to write.

Điều tốt đẹp về Groovy là bạn có thể viết đĩa nồi hơi của bạn một lần và sau đó gọi nó liên tục. Một mô hình tôi đã nhìn thấy rất nhiều, và hiện đang sử dụng, là một cái gì đó như thế này:

private void validate(Long id, Closure closure) { 
    try { 
     closure() 
    } catch (ValidationException e) { 
     def email = Email.read(id) 
     email.errors = e.errors 
     render view: "edit", model: [emailInstance: email] 
    } catch (OtherException e) { 
     def email = Email.read(id) 
     flash.error = "${e.message}: ${e.reasons}" 
     render view: "show", model: [emailInstance: email] 
    } catch (Throwable t) { 
     flash.error = "Unexpected error $t: ${t.message}" 
     redirect action: "list" 
    } 
} 

Và sau đó gọi nó trong mỗi hành động điều khiển như vậy:

def update(Long id, Long version) { 
    withInstance(id, version) { Email emailInstance -> 
     validate(emailInstance.id) { 
      emailService.update(emailInstance, params) 
      flash.message = "Email $id updated at ${new Date()}." 
      redirect action: "show", id: emailInstance.id 
     } 
    } 
} 

(withInstance là một phương pháp tương tự DRYs up kiểm tra cho sự tồn tại và khóa lạc quan.)

Cách tiếp cận này có nhược điểm. Bạn nhận được cùng một bộ chuyển hướng trong mọi hành động; bạn có thể muốn viết một tập các phương thức cho mỗi bộ điều khiển; và nó có vẻ ngớ ngẩn để vượt qua một đóng cửa vào một phương pháp và mong đợi phương pháp để biết những gì ngoại lệ đóng cửa sẽ ném. Nhưng hey, lập trình của tất cả về sự cân bằng, phải không?

Dù sao, hy vọng ít nhất là thú vị.

+0

Đã giúp rất nhiều, nhưng tôi thất vọng với bản mẫu cần thiết để triển khai bất kỳ xác thực hợp lệ nào (không phù hợp với xác thực GORM). –

+0

Tôi nên lưu ý rằng tôi đang bị mắc kẹt với phiên bản 2.2.0, vì vậy một số điều này có thể không cần thiết trong các phiên bản mới hơn của Grails. –

+0

Sử dụng laat 2.X và vẫn cần thiết –

0

Nếu bạn có một dịch vụ như: Trong ứng dụng Grails 2, cách được khuyến nghị sẽ là sử dụng transactionStatus.setRollbackOnly().

import grails.transaction.Transactional 

Class RoleService { 

    @Transactional 
    Role save(String authority) { 
     Role roleInstance = new Role(authority: authority) 
     if (!roleInstance.save()) { 
      // log errors here 
      transactionStatus.setRollbackOnly() 
     } 
     roleInstance 
    } 

} 

Xem: https://github.com/grails/grails-core/issues/9212

Các vấn đề liên quan