達成協議:我遇到過一些團隊,他們在使用SOA技術時由于其工具的復雜性而陷入泥潭。 我只在Java中看到過這種情況,但是我從一些C#開發人員那里聽說,他們也意識到那里的現象。 我想探索一種替代方法。
與向您的項目中添加WSDL(Web服務定義語言。Hocuspocus)文件并自動生成內容相比,此方法需要更多的工作。 但是,它帶有更多的了解和增強的可測試性。 最后,我體驗到,盡管需要額外的體力勞動,但是這使我能夠更快地完成任務。
這篇博客文章的目的(如果您喜歡它,它是擴展的)旨在探索一種總體上對SOA特別是對Web服務的更簡單的方法。 我通過一個具體的例子來說明這些原理:當用戶的貨幣相對于美元跌至閾值以下時,通知用戶。 為了使服務在技術上變得有趣,我將使用訂戶的IP地址來確定其幣種。
步驟1:通過模擬外部交互來創建活動服務
模擬您自己的服務的活動可以幫助您構建定義與外部服務的交互的接口。
預告片:
public class CurrencyPublisherTest {private SubscriptionRepository subscriptionRepository = mock(SubscriptionRepository.class);private EmailService emailService = mock(EmailService.class);private CurrencyPublisher publisher = new CurrencyPublisher();private CurrencyService currencyService = mock(CurrencyService.class);private GeolocationService geolocationService = mock(GeolocationService.class);@Testpublic void shouldPublishCurrency() throws Exception {Subscription subscription = TestDataFactory.randomSubscription();String location = TestDataFactory.randomCountry();String currency = TestDataFactory.randomCurrency();double exchangeRate = subscription.getLowLimit() * 0.9;when(subscriptionRepository.findPendingSubscriptions()).thenReturn(Arrays.asList(subscription));when(geolocationService.getCountryByIp(subscription.getIpAddress())).thenReturn(location);when(currencyService.getCurrency(location)).thenReturn(currency);when(currencyService.getExchangeRateFromUSD(currency)).thenReturn(exchangeRate);publisher.runPeriodically();verify(emailService).publishCurrencyAlert(subscription, currency, exchangeRate);}@Beforepublic void setupPublisher() {publisher.setSubscriptionRepository(subscriptionRepository);publisher.setGeolocationService(geolocationService);publisher.setCurrencyService(currencyService);publisher.setEmailService(emailService);}
}
Spoiler:最近,我開始將隨機測試數據生成用于我的測試,效果很好。
發布者使用了許多服務。 現在讓我們集中討論一項服務:GeoLocationService。

步驟2:為每個服務創建測試和存根-從geolocationservice開始
頂級測試顯示了我們從每個外部服務中需要的東西。 得知此信息并閱讀(是!)服務的WSDL后,我們可以測試驅動服務的存根。 在此示例中,我們實際上通過啟動嵌入在測試中的Jetty使用HTTP運行測試。
預告片:
public class GeolocationServiceStubHttpTest {@Testpublic void shouldAnswerCountry() throws Exception {GeolocationServiceStub stub = new GeolocationServiceStub();stub.addLocation("80.203.105.247", "Norway");Server server = new Server(0);ServletContextHandler context = new ServletContextHandler();context.addServlet(new ServletHolder(stub), "/GeoService");server.setHandler(context);server.start();String url = "http://localhost:" + server.getConnectors()[0].getLocalPort();GeolocationService wsClient = new GeolocationServiceWsClient(url + "/GeoService");String location = wsClient.getCountryByIp("80.203.105.247");assertThat(location).isEqualTo("Norway");}
}
驗證并創建xml有效負載
這是第一個“裸露的”位。 在這里,我不使用框架就創建了XML有效負載(俗稱的“ $”語法由JOOX庫提供,JOOX庫是內置JAXP類之上的一個薄包裝):

我將用于實際服務的XSD(更多焦點)添加到項目中,并編寫代碼以驗證消息。 然后,通過遵循驗證錯誤開始構建XML有效負載。
預告片:
public class GeolocationServiceWsClient implements GeolocationService {private Validator validator;private UrlSoapEndpoint endpoint;public GeolocationServiceWsClient(String url) throws Exception {this.endpoint = new UrlSoapEndpoint(url);validator = createValidator();}@Overridepublic String getCountryByIp(String ipAddress) throws Exception {Element request = createGeoIpRequest(ipAddress);Document soapRequest = createSoapEnvelope(request);validateXml(soapRequest);Document soapResponse = endpoint.postRequest(getSOAPAction(), soapRequest);validateXml(soapResponse);return parseGeoIpResponse(soapResponse);}private void validateXml(Document soapMessage) throws Exception {validator.validate(toXmlSource(soapMessage));}protected Validator createValidator() throws SAXException {SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);Schema schema = schemaFactory.newSchema(new Source[] {new StreamSource(getClass().getResource("/geoipservice.xsd").toExternalForm()),new StreamSource(getClass().getResource("/soap.xsd").toExternalForm()),});return schema.newValidator();}private Document createSoapEnvelope(Element request) throws Exception {return $("S:Envelope",$("S:Body", request)).document();}private Element createGeoIpRequest(String ipAddress) throws Exception {return $("wsx:GetGeoIP", $("wsx:IPAddress", ipAddress)).get(0);}private String parseGeoIpResponse(Element response) {// TODOreturn null;}private Source toXmlSource(Document document) throws Exception {return new StreamSource(new StringReader($(document).toString()));}
}
在這個示例中,我從JOOX庫中獲得了一些幫助(也有些痛苦),用于Java中的XML操作。 由于Java的XML庫非常瘋狂,因此我也放棄了已檢查的異常。
Spoiler:我對到目前為止發現的所有XML庫中的名稱空間,驗證,XPath和檢查的異常的處理通常感到非常不滿意。 所以我正在考慮創建自己的。
當然,您可以對從XSD自動生成的類使用相同的方法,但是我不相信這確實有很大幫助。
通過http流xml
Java內置的HttpURLConnection是一種笨拙但可維修的將XML傳送到服務器的方法(只要您不執行高級HTTP身份驗證)。

預告片:
public class UrlSoapEndpoint {private final String url;public UrlSoapEndpoint(String url) {this.url = url;}public Document postRequest(String soapAction, Document soapRequest) throws Exception {URL httpUrl = new URL(url);HttpURLConnection connection = (HttpURLConnection) httpUrl.openConnection();connection.setDoInput(true);connection.setDoOutput(true);connection.addRequestProperty("SOAPAction", soapAction);connection.addRequestProperty("Content-Type", "text/xml");$(soapRequest).write(connection.getOutputStream());int responseCode = connection.getResponseCode();if (responseCode != 200) {throw new RuntimeException("Something went terribly wrong: " + connection.getResponseMessage());}return $(connection.getInputStream()).document();}
}
破壞者:此代碼應通過日志記錄和錯誤處理進行擴展,并將驗證移入裝飾器中。 通過控制HTTP處理,我們可以解決人們購買ESB所需解決的大多數問題。
創建存根并解析xml
存根使用xpath在請求中查找位置。 它生成響應的方式與ws客戶端生成請求的方式幾乎相同(未顯示)。

public class GeolocationServiceStub extends HttpServlet {private Map<String,String> locations = new HashMap<String, String>();public void addLocation(String ipAddress, String country) {locations.put(ipAddress, country);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {String ipAddress = $(req.getReader()).xpath("/Envelope/Body/GetGeoIP/IPAddress").text();String location = locations.get(ipAddress);createResponse(location).write(resp.getOutputStream());} catch (Exception e) {throw new RuntimeException("Exception at server " + e);}}
}
劇透:可以將存根擴展到一個網頁,使我可以測試系統,而無需實際集成到任何外部服務。
驗證并解析響應
ws客戶端現在可以驗證存根的響應是否符合XSD并解析響應。 同樣,這是使用XPath完成的。 我沒有顯示代碼,只是更多相同。
真實的東西!
現在,該代碼將驗證XML有效負載是否符合XSD。 這意味著ws客戶端現在應該可以用于真實對象。 讓我們編寫一個單獨的測試來檢查它:

public class GeolocationServiceLiveTest {@Testpublic void shouldFindLocation() throws Exception {GeolocationService wsClient = new GeolocationServiceWsClient("http://www.webservicex.net/geoipservice.asmx");assertThat(wsClient.getCountryByIp("80.203.105.247")).isEqualTo("Norway");}}
好極了! 有用! 實際上,它在我第一次嘗試時失敗了,因為我沒有正確的國家名稱作為測試的IP地址。
這種點對點集成測試比我的其他單元測試慢且健壯。 但是,我認為事實并不重要。 我從Infinitest配置中過濾了測試,除此之外我不在乎。
充實所有服務
需要以與GeolocationService相同的方式充實SubscriptionRepository,CurrencyService和EmailService。 但是,由于我們知道我們只需要與這些服務中的每一個進行非常特定的交互,因此我們不必擔心作為SOAP服務的一部分可能發送或接收的所有內容。 只要我們能夠完成業務邏輯(CurrencyPublisher)所需的工作,我們就很好!
示范和價值鏈測試
如果我們為存根創建Web UI,我們現在可以向客戶展示此服務的整個價值鏈。 在我的SOA項目中,我們依賴的某些服務將僅在項目后期才能上線。 在這種情況下,我們可以使用存根顯示我們的服務有效。
劇透:隨著我厭倦了驗證手動價值鏈測試的有效性,我可能最終會創建一個使用WebDriver設置存根并驗證測試可以正常進行的測試,就像在手動測試中一樣。
在Soa競技場中戰斗時脫下手套
在本文中,我展示并暗示了六種以上的技術,這些技術可用于不涉及框架,ESB或代碼生成的測試,http,xml和驗證。 該方法使程序員可以100%控制他們在SOA生態系統中的位置。 每個領域都需要深入探索。 如果您想探索它,請告訴我。
哦,我也想使用更好的Web服務的想法,因為Geolocated貨幣電子郵件非常實用。
參考: 預告片:來自我們JCG合作伙伴 Johannes Brodwall的“千篇一律的SOA”,來自“更大的盒子中的思考”博客。
翻譯自: https://www.javacodegeeks.com/2012/07/teaser-bare-knuckle-soa.html