Bare-knuckle web development

4,999 views
4,825 views

Published on

The Bare-Bones Live Coding Demonstration (as given at XP Days 2012 in Kiev)

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,999
On SlideShare
0
From Embeds
0
Number of Embeds
205
Actions
Shares
0
Downloads
4
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Bare-knuckle web development

  1. 1. Bare-knuckle web development XP Days Ukraine Johannes Brodwall, Chief scientist Exilesoft Global
  2. 2. • Bare-knuckle philosophy• Demonstration of bare- knuckle web in Java • Further directions
  3. 3. Part I:
  4. 4. The bare-knuckle philosophy
  5. 5. High impact with low ceremony
  6. 6. • Framework light • Test-driven • No calculators
  7. 7. Light on framework
  8. 8. Frameworks solve 80% of the job…
  9. 9. … and makes the rest 10 times as hard
  10. 10. “Why did Hibernatesuddenly slow down?”
  11. 11. “How do I implement a custom SOAP header with JAX-WS?”
  12. 12. “How to do X with Spring”
  13. 13. @AutoWire + package scan with 100s of beans
  14. 14. Test-driven
  15. 15. No more architecture than what’s needed
  16. 16. Fast feedback cycle – also in the future
  17. 17. Don’t use acalculator…
  18. 18. Part II:
  19. 19. Demo: Phonebook web app
  20. 20. Test driving
  21. 21. WebDriver browser = createWebDriver();browser.get(url);browser.findElement(By.linkText("Add contact")).click();browser.findEleme(By.name("fullName")).sendKeys("Vader");browser.findEleme(By.name("phoneNumber")).sendKeys("27");browser.findEleme(By.name("saveContact")).click();browser.findElement(By.linkText("Find contact")).click();browser.findElem(By.name("nameQuery")).sendKeys("vader");browser.findElement(By.name("nameQuery")).submit();assertThat(browser.findElem(By.id("contacts")).getText()) .contains("555-33274-7827");
  22. 22. Server server = new Server(0);server.setHandler( new WebAppContext("src/main/webapp", "/contacts"));server.start();int port = server.getConnectors()[0].getLocalPort();String url = "http://localhost:" + port + "/contacts";
  23. 23. <web-app version="2.5“><servlet> <servlet-name>contactServlet</servlet-name> <servlet-class> com.exilesoft.bareknuckleweb.ContactServlet </servlet-class></servlet><servlet-mapping> <servlet-name>contactServlet</servlet-name> <url-pattern>contact/*</url-pattern></servlet-mapping></web-app>
  24. 24. public class ContactServlet extends HttpServlet {}
  25. 25. @Testpublic void shouldShowAddForm() throws Exception { ContactServlet servlet = new ContactServlet(); HttpServletRequest req = mock(HttpServletRequest.class); HttpServletResponse resp = mock(HttpServletResponse.class); StringWriter html = new StringWriter(); when(resp.getWriter()).thenReturn(new PrintWriter(html)); when(req.getPathInfo()).thenReturn("/create.html"); servlet.doGet(req, resp); verify(resp).setContentType("text/html"); assertThat(html.toString()) .contains("<form method=post") .contains("<input type=text name=fullName") .contains("<input type=text name=phoneNumber") .contains("<input type=submit name=createContact");}
  26. 26. Refactoring
  27. 27. void showFindPage(String q, PrintWriter writer) { Document doc = Xml.read("contact/index.html"); doc.selectFirst("[name=nameQuery]").val(nameQuery); Element contactsEl = doc.select("#contacts"); Element template = contactsEl.select(".contact"); contactsEl.clear(); for (Contact contact : contactRepository.find(q)) { contactsEl.add( template.copy().text(contact.print())); } doc.write(writer);}
  28. 28. .NET
  29. 29. [TestMethod]public void ShouldFindSavedContacts(){ var server = new WebServer(); server.Start("http://localhost:12380/"); var url = "http://localhost:12380"; var browser = new SimpleBrowser.WebDriver.SimpleBrowserDriver(); browser.Url = url + "/contacts"; browser.FindElement(By.LinkText("Add contact")).Click(); browser.FindElement(By.Name("fullName")).SendKeys("Darth Vader"); browser.FindElement(By.Name("phoneNumber")).SendKeys("555-33274-7827"); browser.FindElement(By.Name("saveContact")).Click(); browser.FindElement(By.LinkText("Find contact")).Click(); browser.FindElement(By.Name("nameQuery")).SendKeys("vader"); browser.FindElement(By.Name("nameQuery")).Submit(); browser.FindElement(By.Id("contacts")).Text.Should() .Contain("555-33274-7827");}
  30. 30. public class WebServer{ public void Start(string baseAddress) { var config = new HttpSelfHostConfiguration(baseAddress); config.Routes.MapHttpRoute( "web Default", "{controller}/{id}", new { id = RouteParameter.Optional }); using (var server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); } }}
  31. 31. Part III:
  32. 32. Further directions
  33. 33. Norwegianagricultural authority
  34. 34. Java web application with an MVC architecture
  35. 35. Controllers: • Create a view• Retrieve model from repo • Set model on view • Render view
  36. 36. View example:
  37. 37. @Overridepublic void render(HttpServletResponse resp) throws IOException { Match document = $("html", head(), $("img").attr("src", "/sms-varsel/Sparebank1.jpg"), $("h1", "Internet bank simulator"), $("form").attr("method", "post").append( hiddenField(this.bankNum, "bankNum"), hiddenField(this.customerId, "customerId"), $("h2", "Set Mobile Phone Number"), phoneNumberField(this.phoneNumber), $("h2", "Account numbers"), accountNumbersField(this.accountNumbers), $("h2", "Payment account"), paymentAccountField(this.defaultAccount), $("h2", "Save changes"), $("div",$("input").attr("type", "submit").attr("value", "Store")).attr("name", "update"))); resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().write(document.toString());}
  38. 38. Match document = $("html", head(), $("img").attr("src", "/logo.jpg"), $("h1", “Page name"), $("form").attr("method", "post").append( hiddenField(this.bankNum, "bankNum"), hiddenField(this.customerId, "customerId"), $("h2", "Save changes"), $("div", $("input").attr("type", "submit") .attr("value", "Store")) .attr("name", "update")));
  39. 39. Norwegian Power TransmissionSystem Operator
  40. 40. Universal repository Universal serviceCommands and Queries One domain model
  41. 41. No Spring – 100 KLOC
  42. 42. Single-jar deployment • Includes scripts • Includes Jetty
  43. 43. public class StatnettWebServer { private final org.eclipse.jetty.server.Server server; public ContactWebServer(int port) { server = new Server(port); server.setHandler(new WebAppContext(“…", "/statnett")); } void start() throws Exception { server.start(); } String getUrl() { int port = server.getConnectors()[0].getLocalPort(); return "http://localhost:" + port + "/contacts"; } public static void main(String[] args) throws Exception { StatnettWebServer server = new StatnettWebServer(10080); server.start(); System.out.println(server.getUrl()); }}
  44. 44. SpareBank1
  45. 45. 10 web service clients
  46. 46. HttpURLConnection JOOX
  47. 47. @Overridepublic String getCountryByIp(String ipAddress) throwsException { Document soapRequest = soapElement("S:Envelope", $("S:Body", wsxElement("wsx:GetGeoIP", $("wsx:IPAddress", ipAddress)))); Document soapResponse endpoint.postRequest(getSOAPAction(), soapRequest); return $(soapResponse).xpath("/Envelope/Body/*") .xpath("GetGeoIPResult/CountryName").text();}
  48. 48. public Document postRequest(String soapAction, Document soapRequest) { HttpURLConnection conn = (HttpURLConnection) url.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 (isErrorResponse(responseCode)) { String response = toString(connection.getErrorStream()); String responseContentType = connection.getContentType(); if (responseContentType.startsWith("text/xml")) { return response; } throw new ServiceCommunicationException( "On POST to " + url + ": " + responseCode + " " + connection.getResponseMessage() + ": " + response); } return $(connection.getInputStream()).document();d}
  49. 49. Conclusion:
  50. 50. YAGNI
  51. 51. No calculator until…
  52. 52. Don’t use a framework you couldn’t have written yourself
  53. 53. Thank you jbr@exilesoft.com http://johannesbrodwall.com http://exilesoft.com http://twitter.com/jhannes

×