![](/img/trans.png)
[英]Where should business logic (and what is that?) really live and how to do that with Spring?
[英]Where Should The Validation Logic Live?
題
如何最好地管理需要復雜驗證邏輯的對象圖的構造? 出於可測試性原因,我想保留依賴注入,無操作構造函數。
可測試性對我來說非常重要,您的建議如何維護代碼的這個屬性?
背景
我有一個普通的java對象,它為我管理一些業務數據的結構:
class Pojo
{
protected final String p;
public Pojo(String p) {
this.p = p;
}
}
我想確保p的格式是有效的,因為如果沒有這種保證,這個業務對象真的沒有意義; 如果p是無意義的話,甚至不應該創建它。 但是,驗證p是非平凡的。
捕獲
真的,它需要足夠復雜的驗證邏輯,邏輯應該完全可以測試它,所以我在一個單獨的類中有這個邏輯:
final class BusinessLogic() implements Validator<String>
{
public String validate(String p) throws InvalidFoo, InvalidBar {
...
}
}
可能重復的問題
我的思考到目前為止
雖然以下構造函數公開聲明Pojo的依賴關系並保留其簡單的可測試性,但它完全不安全。 這里沒有任何東西阻止客戶提供一個聲稱每個輸入都可以接受的驗證器!
public Pojo(Validator businessLogic, String p) throws InvalidFoo, InvalidBar {
this.p = businessLogic.validate(p);
}
所以,我稍微限制了構造函數的可見性,並提供了一個工廠方法,確保驗證然后構造:
@VisibleForTesting
Pojo(String p) {
this.p = p;
}
public static Pojo createPojo(String p) throws InvalidFoo, InvalidBar {
Validator businessLogic = new BusinessLogic();
businessLogic.validate(p);
return new Pojo(p);
}
現在我可以將createPojo重構為工廠類,這將恢復Pojo的“單一責任”並簡化工廠邏輯的測試,更不用說不再浪費在每個新Pojo上創建新的(無狀態)BusinessLogic的性能優勢。
我的直覺是我應該停下來,要求外界的意見。 我在正確的軌道上嗎?
下面的一些回應要素......讓我知道它是否有意義/回答你的問題。
簡介 :我認為您的系統可以是一個簡單的庫,一個多層應用程序,也可以是一個復雜的分布式系統,它在驗證時實際上沒有太大區別:
在哪里驗證?
您通常希望驗證輸入參數:
在客戶端,在將參數傳遞給服務之前,要確保早期對象在線下有效。 如果它是遠程服務或者在參數的生成和對象創建之間存在復雜的流程,則這是特別期望的。
在服務方面:
Pojo
); 如何驗證?
假設如上所述,由於您可能必須在多個位置重用驗證邏輯,因此在實用程序類中提取它可能是個好主意。 這條路:
更具體地說,您至少會在構造函數中調用此邏輯,以強制實現對象的有效性(考慮將有效的依賴項作為Pojo
方法中的算法的前提條件 ):
實用類 :
public final class PojoValidator() {
private PojoValidator() {
// Pure utility class, do NOT instantiate.
}
public static String validateFoo(final String foo) {
// Validate the provided foo here.
// Validation logic can throw:
// - checked exceptions if you can/want to recover from an invalid foo,
// - unchecked exceptions if you consider these as runtime exceptions and can't really recover (less clutering of your API).
return foo;
}
public static Bar validateBar(final Bar bar) {
// Same as above...
// Can itself call other validators.
return bar;
}
}
Pojo類 :注意靜態導入語句以提高可讀性。
import static PojoValidator.validateFoo;
import static PojoValidator.validateBar;
class Pojo {
private final String foo;
private final Bar bar;
public Pojo(final String foo, final Bar bar) {
validateFoo(foo);
validateBar(bar);
this.foo = foo;
this.bar = bar;
}
}
如何測試我的Pojo?
您應該添加創建單元測試以確保在構造時調用驗證邏輯(以避免回歸,因為人們稍后通過刪除原因X,Y,Z的驗證邏輯來“簡化”構造函數)。
如果它們很簡單,可以內聯創建依賴項,因為它使您的測試更具可讀性,因為您使用的所有內容都是本地的(滾動較少,心理占用空間較小等)
但是,如果您的Pojo依賴項的設置足夠復雜/冗長以至於測試不再可讀,您可以將此設置考慮在@Before
/ setUp
方法中,以便測試Pojo
邏輯的單元測試真正專注於驗證你的Pojo的行為。
無論如何,我同意Jeff Storey:
最后,將您的測試視為代碼示例,示例或可執行規范:您不希望通過以下方式給出一個令人困惑的示例:
如果Pojo
需要非常復雜的依賴項怎么辦?
[ 如果是這種情況,請告訴我們 ]
生產代碼 :您可以嘗試在工廠中隱藏這種復雜性。
測試代碼 :要么:
編輯:一些關於安全上下文中的輸入驗證的鏈接,這也是有用的:
首先,我要說驗證邏輯不應該只存在於客戶端。 這有助於確保您不會在數據存儲中放入無效數據。 如果您添加其他客戶端(例如,您可能有一個胖客戶端,並且您添加了一個Web服務客戶端,則需要在兩個位置維護驗證)。
我不認為你應該有一個構造函數來構造一個不驗證的對象(使用@VisibleForTesting
注釋)。 您通常應該使用有效對象進行測試(除非您正在測試錯誤情況)。 此外,在生產代碼中添加僅用於測試的其他代碼是代碼味道,因為它不是真正的生產代碼。
我認為放置驗證邏輯的適當位置在域對象本身內。 這將確保您不會創建無效的域對象。
我真的不喜歡將驗證器傳遞給域對象的想法。 這為需要了解驗證器的域對象的客戶端提供了大量工作。 如果你想創建一個單獨的驗證器,這可能會增加重用和模塊化的好處,但我不會注入它。 在測試中,您始終可以使用完全繞過驗證的模擬對象。
向域模型添加驗證是Web框架常見的(grails / rails)。 我認為這是一個很好的方法,它不應該損害可測試性。
我想確保
p
的格式是有效的,因為如果沒有這種保證,這個業務對象真的沒有意義; 如果p
是無意義的話,甚至不應該創建它。 但是,驗證p
是非平凡的。
p
”意味着p
的類型不僅僅是String
,而是一些更具體的類型。 例如, EmailString
:
class Pojo
{
protected final EmailString p;
public Pojo(EmailString p) {
this.p = p;
}
}
String
和EmailString
之間必須有一個轉換器。 這個轉換器作為業務邏輯的一部分,確保p
不能無效。 每當消費者擁有String
並想要創建Pojo
他就會使用這個轉換器。 有幾種變體如何實現此轉換器以方便地捕獲不可轉換的情況,例如.net
樣式:
class Converter
{
public static bool TryParse(String s, out EmailString p) {
}
}
可測試性對我來說非常重要,您的建議如何維護代碼的這個屬性?
這樣,您可以分別從String
解析/轉換邏輯測試Pojo
邏輯。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.