定制基元和DTO的(反)序列化和验证
最近,我們為您提供了新的HTTP框架HttpMate。 在引言文章中 ,我們將請求和響應映射到域對象稱為“最復雜的技術細節”,以及如何通過另一個伴侶MapMate幫助我們。
實際上,在將請求屬性映射到您的域對象時,MapMate減輕了HttpMate的負擔。 它負責將響應轉換為適當的格式(JSON,XML,YAML等),從本質上執行反序列化和序列化,但還要執行更多的工作。
在本文中,我們將重點介紹MapMate如何幫助我們以可控和可預測的方式處理(反序列化)請求/響應對象。
自定義基元
讓我們回顧一下上一篇文章中的示例; 我們有一個簡單的UseCase發送電子郵件。 為此,我們需要一個Email對象,該對象應具有:
- 發件人
- 接收者
- 學科
- 身體
所有這些字段都可以表示為字符串,甚至可以表示為字節數組。 選擇用來表示數據的通用類型越多,以后解釋它的可能性就越大。 想象一下以下方法:
public Object sendEmail(final Object sender, final Object receiver, final Object subject, final Object body) {... }這給我們留下了很多未解決的問題:
- 是發件人instanceOf字符串還是字節[]?
- 編碼是什么?
- 拉鏈被壓縮了嗎?
清單繼續。 盡管在某些情況下這可能是適當的,但我敢打賭,您會更滿意:
public String sendEmail(final String sender, final String receiver, final String subject, final String body) {... }后者留出了較少的解釋空間:例如,我們不再需要假設編碼或完全質疑參數的類型。
但是,它仍然是模棱兩可的,發件人字段是否帶有用戶名或她的電子郵件地址? 同樣的歧義是編寫單元測試時產生無限不確定性的原因……在某種程度上,使用隨機字符串生成器測試了一種方法,人們知道該方法只能接受電子郵件地址。
對于人員和編譯器,以下方法簽名在歧義方面做得更好:
public Receipt sendEmail(final EmailAddress sender, final EmailAddress receiver, final Subject subject, final Body body) {... }我們可以以相同的方式相信字符串是字符串,整數是整數,現在我們可以相信EmailAddress是電子郵件地址,主題實際上是主題–它們成為了send email方法的自定義原語。
發件人和接收者不是面面俱到的“字符串”,它們與“主題”和“正文”有很大不同。 它們是電子郵件地址,我們可以通過使用一些理智的正則表達式來驗證其值來表示它們。 (提防ReDoS )
使用工廠方法作為創建“始終有效”對象的方法的合理性已得到廣泛討論和驗證。 考慮到這一點,我們將為示例用例創建一個EmailAddress類,然后將其用作Sender和Receiver字段的自定義原始類型。
public final class EmailAddress {private final String value;private EmailAddress(final String value) {this.value = value;}public static EmailAddress fromStringValue(final String value) {final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");return new EmailAddress(validated);} }由于–唯一的實例變量是私有的且是最終變量,因此只能使用私有的構造函數進行賦值,該私有構造函數只能在驗證輸入之前使用公共工廠方法從類外部調用,該方法會驗證輸入,然后將其傳遞給構造函數–我們可以請確保每當我們收到EmailAddress實例時,它都是有效的。
如果您現在對EmailAddressValidator實現感到好奇,請確保簽出此示例項目的源代碼 。
現在,我們的域對象不僅可以使用默認原語(例如String,Double,Integer等),還可以使用自定義原語(例如EmailAddress和Body,Subject等)。通常,盡管我們需要能夠將域對象存儲在數據庫中或將其傳達給其他服務或UI。 盡管沒有其他方知道名為EmailAddress的自定義基元。 因此,我們需要它的“表示形式”,即HTTP,持久性和人性化的東西–字符串。
public final class EmailAddress {private final String value;public static EmailAddress fromStringValue(final String value) {final String validated = EmailAddressValidator.ensureEmailAddress(value, "emailAddress");return new EmailAddress(validated);}public String stringValue() {return this.value;} }我們添加的方法“ stringValue”是自定義基元的字符串表示形式。 現在,我們可以發送EmailAddress的“ stringValue”,然后根據接收到的值對其進行重構。 本質上,“ fromString”和“ stringValue”方法分別是EmailAddress的“反序列化”和“序列化”機制。
按照這種方法,我們還可以為電子郵件的正文和主題創建自定義基元:
public final class Body {private final String value;public static Body fromStringValue(final String value) {final String emailAddress = LengthValidator.ensureLength(value, 1, 1000, "body");return new Body(emailAddress);}public String stringValue() {return this.value;} }public final class Subject {private final String value;public static Subject fromStringValue(final String value) {final String validated = LengthValidator.ensureLength(value, 1, 256, "subject");return new Subject(validated);}public String stringValue() {return this.value;} }數據傳輸對象
有了我們的自定義基元,我們現在可以創建適當的數據傳輸對象–電子郵件,這是非常簡單的任務,因為它基本上是一個不變的結構:
public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body; }相同的“始終有效”的方法也適用于數據傳輸對象,除了在這里,由于我們利用了自定義基元,因此時間更短。
DTO的工廠方法可以像驗證強制字段的存在一樣簡單,也可以像應用跨字段驗證一樣復雜。
public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body;public static Email restore(final EmailAddress sender,final EmailAddress receiver,final Subject subject,final Body body) {RequiredParameterValidator.ensureNotNull(sender, "sender");RequiredParameterValidator.ensureNotNull(receiver, "receiver");RequiredParameterValidator.ensureNotNull(body, "body");return new Email(sender, receiver, subject, body); }不幸的是,現代(反)序列化和驗證框架無法與這種DTO一起使用。
這是一個JSON示例,如果您使用默認配置將電子郵件DTO饋送到這樣的框架,則可能會獲得最佳效果:
{"sender": {"value": "sender@example.com"},"receiver": {"value": "receiver@example.com"},"subject": {"value": "subject"},"body": {"value": "body"} }雖然人們期望的是:
{"sender": "sender@example.com","receiver": "receiver@example.com","subject": "subject","body": "body" }盡管可以使用大量樣板代碼來緩解此問題,但是驗證是另一種野獸,當您想從服務器“立即報告所有驗證錯誤”時,驗證就變得致命。 為什么不立即告訴用戶發送方和接收方均無效,而不是發送尋求許可A38的請求發送給用戶。 實際上,這就是我們在嘗試編寫現代微服務時,同時又遵循Clean Code的最佳實踐的感覺,Domain Driven Design,Domain Driven Security的“始終有效”方法……
這就是MapMate需要解決的問題。
MapMate
與HttpMate一樣,我們確保提供一個易于構建的構建器,同時保留細粒度定制的可能性。 這是使我們的電子郵件示例序列化,反序列化和驗證我們的自定義基元和DTO的絕對最低配置。
public static MapMate mapMate() {return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).build(); }這部分將使以下JSON成為有效請求:
{"sender": "sender@example.com","receiver": "receiver@example.com","subject": "Hello world!","body": "Hello from Sender to Receiver!" }您必須指定要掃描(遞歸)的程序包和一對(非)編組器。 可以是任何可以從Map生成字符串的內容,反之亦然。 這是使用ObjectMapper的示例:
final ObjectMapper objectMapper = new ObjectMapper(); return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(value -> {try {return objectMapper.writeValueAsString(value);} catch (JsonProcessingException e) {throw new UnsupportedOperationException("Could not parse value " + value, e);}}, new Unmarshaller() {@OverridepublicT unmarshal(final String input, final Classtype) {try {return objectMapper.readValue(input, type);} catch (final IOException e) {throw new UnsupportedOperationException("Could not parse value " + input + " to type " + type, e);}}}).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build(); 承諾的驗證異常聚合又如何呢?
在我們的示例中,如果自定義原語或DTO無效,則所有驗證都返回CustomTypeValidationException的實例。
添加以下行,以指示MapMate將您的Exception類識別為驗證錯誤的指示。
public static MapMate mapMate() {return MapMate.aMapMate("com.envimate.examples.email_use_case").usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build(); }現在,如果我們嘗試以下請求:
{"sender": "not-a-valid-sender-value","receiver": "not-a-valid-receiver-value","subject": "Hello world!","body": "Hello from Sender to Receiver!" }我們將收到以下答復:
HTTP/1.1 400 Bad Request Date: Tue, 04 Jun 2019 18:30:51 GMT Transfer-encoding: chunked{"message":"receiver: Invalid email address: 'not-a-valid-receiver-value',sender: Invalid email address: 'not-a-valid-sender-value'"}最后的話
此處提供的MapMate構建器可簡化初始使用。 但是,所有描述的默認值都是可配置的,此外,您還可以從“自定義基元”和“ DTO”中排除包和類,可以配置哪些異常被視為“驗證錯誤”以及如何對其進行處理,還可以為“自定義”指定其他方法名稱原始序列化,或者提供您的lambda來同時進行這兩個序列的反序列化。
有關MapMate的更多示例和詳細信息,請查看MapMate存儲庫 。
讓我們知道您的想法以及您接下來想在MapMate中看到的功能!
翻譯自: https://www.javacodegeeks.com/2019/08/deserialization-and-validation-of-custom-primitives-and-dtos.html
總結
以上是生活随笔為你收集整理的定制基元和DTO的(反)序列化和验证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 针对新手的Java EE7和Maven项
- 下一篇: 鲈鱼产于哪里 鲈鱼的原产地