Bareknuckle web development Odessa Johannes Brodwall Chief scientist
Bare-knuckle web development Odessa Johannes Brodwall, Chief scientist Exilesoft Global
• Bare-knuckle philosophy • Demonstration of bareknuckle web in Java • Further directions
Part I:
The bare-knuckle philosophy
• High impact/low ceremony • Framework light • Test-driven
High impact with low ceremony
Java web: Servlets, Web. Driver, Jetty, Mockito
Java SOAP: JOOX, Http. URLConnection
. NET web prototype: Web. Driver + Http. Listener
. NET web work-inprogress: Web. Driver + Http. Self. Host. Server
Light on framework
Frameworks solve 80% of the job…
… and makes the rest 10 times as hard
“Why did Hibernate suddenly slow down? ”
“How do I implement a custom SOAP header with JAX-WS? ”
“How to do X with Spring”
@Auto. Wire + package scan with 100 s of beans
Test-driven
No more architecture than what’s needed
Fast feedback cycle – also in the future
Part II:
Demo: Phonebook web app
Test driving
Web. Driver browser = create. Web. Driver(); browser. get(url); browser. find. Element(By. link. Text("Add contact")). click(); browser. find. Eleme(By. name("full. Name")). send. Keys("Vader"); browser. find. Eleme(By. name("phone. Number")). send. Keys("27"); browser. find. Eleme(By. name("save. Contact")). click(); browser. find. Element(By. link. Text("Find contact")). click(); browser. find. Elem(By. name("name. Query")). send. Keys("vader"); browser. find. Element(By. name("name. Query")). submit(); assert. That(browser. find. Elem(By. id("contacts")). get. Text()). contains("555 -33274 -7827");
Server server = new Server(0); server. set. Handler( new Web. App. Context("src/main/webapp", "/contacts")); server. start(); int port = server. get. Connectors()[0]. get. Local. Port(); String url = "http: //localhost: " + port + "/contacts";
<web-app version="2. 5“> <servlet-name>contact. Servlet</servlet-name> <servlet-class> com. exilesoft. bareknuckleweb. Contact. Servlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>contact. Servlet</servlet-name> <url-pattern>contact/*</url-pattern> </servlet-mapping> </web-app>
public class Contact. Servlet extends Http. Servlet { }
@Test public void should. Show. Add. Form() throws Exception { Contact. Servlet servlet = new Contact. Servlet(); Http. Servlet. Request req = mock(Http. Servlet. Request. class); Http. Servlet. Response resp = mock(Http. Servlet. Response. class); String. Writer html = new String. Writer(); when(resp. get. Writer()). then. Return(new Print. Writer(html)); when(req. get. Path. Info()). then. Return("/create. html"); servlet. do. Get(req, resp); verify(resp). set. Content. Type("text/html"); assert. That(html. to. String()). contains("<form method='post'"). contains("<input type='text' name='full. Name'"). contains("<input type='text' name='phone. Number'"). contains("<input type='submit' name='create. Contact'"); }
Refactoring
Part III:
Further directorions
Norwegian agricultural authority
Java web application with an MVC architecture
• Controllers: • Create a view Retrieve model from repo • Set model on view • Render view
View example:
@Override public void render(Http. Servlet. Response resp) throws IOException { Match document = $("html", head(), $("img"). attr("src", "/sms-varsel/Sparebank 1. jpg"), $("h 1", "Internet bank simulator"), $("form"). attr("method", "post"). append( hidden. Field(this. bank. Num, "bank. Num"), hidden. Field(this. customer. Id, "customer. Id"), $("h 2", "Set Mobile Phone Number"), phone. Number. Field(this. phone. Number), $("h 2", "Account numbers"), account. Numbers. Field(this. account. Numbers), $("h 2", "Payment account"), payment. Account. Field(this. default. Account), $("h 2", "Save changes"), $("div", $("input"). attr("type", "submit"). attr("value", "Store")). attr("name", "update"))); resp. set. Content. Type("text/html"); resp. set. Character. Encoding("UTF-8"); resp. get. Writer(). write(document. to. String()); }
Match document = $("html", head(), $("img"). attr("src", "/logo. jpg"), $("h 1", “Page name"), $("form"). attr("method", "post"). append( hidden. Field(this. bank. Num, "bank. Num"), hidden. Field(this. customer. Id, "customer. Id"), $("h 2", "Save changes"), $("div", $("input"). attr("type", "submit"). attr("value", "Store")). attr("name", "update")));
Norwegian Power Transmission System Operator
Universal repository Universal service Commands and Queries One domain model
No Spring – 100 KLOC
Single-jar deployment • Includes scripts • Includes Jetty
public class Statnett. Web. Server { private final org. eclipse. jetty. server. Server server; public Contact. Web. Server(int port) { server = new Server(port); server. set. Handler(new Web. App. Context(“…", "/statnett")); } void start() throws Exception { server. start(); } String get. Url() { int port = server. get. Connectors()[0]. get. Local. Port(); return "http: //localhost: " + port + "/contacts"; } public static void main(String[] args) throws Exception { Statnett. Web. Server server = new Statnett. Web. Server(10080); server. start(); System. out. println(server. get. Url()); } }
Spare. Bank 1
10 web service clients
Http. URLConnection JOOX
@Override public String get. Country. By. Ip(String ip. Address) throws Exception { Document soap. Request = soap. Element("S: Envelope", $("S: Body", wsx. Element("wsx: Get. Geo. IP", $("wsx: IPAddress", ip. Address)))); Document soap. Response endpoint. post. Request(get. SOAPAction(), soap. Request); return $(soap. Response). xpath("/Envelope/Body/*"). xpath("Get. Geo. IPResult/Country. Name"). text(); }
public Document post. Request(String soap. Action, Document soap. Request) { Http. URLConnection conn = (Http. URLConnection) url. open. Connection(); connection. set. Do. Input(true); connection. set. Do. Output(true); connection. add. Request. Property("SOAPAction", soap. Action); connection. add. Request. Property("Content-Type", "text/xml"); $(soap. Request). write(connection. get. Output. Stream()); int response. Code = connection. get. Response. Code(); if (is. Error. Response(response. Code)) { String response = to. String(connection. get. Error. Stream()); String response. Content. Type = connection. get. Content. Type(); if (response. Content. Type. starts. With("text/xml")) { return response; } throw new Service. Communication. Exception( "On POST to " + url + ": " + response. Code + " " + connection. get. Response. Message() + ": " + response); } return $(connection. get. Input. Stream()). document(); d}
Conclusion:
YAGNI
Test-driven development High investment in tests Low investment in frameworks
Thank you jbr@exilesoft. com http: //johannesbrodwall. com http: //exilesoft. com http: //twitter. com/jhannes
- Slides: 51