达成协议:我遇到过一些团队,他们在使用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