Farewell Old Application Server The Server Container Anti- Pattern and how to avoid it
(Shameless plug)
.meetup.com/13
Why am I standing here?
I believe
that you, as a developer,
should care about deployment.
Two things you will learn:
1. How to create a non-server
2. Practical Configuration Management
My story:
By company decree
we were using a traditional application server.
(WebSphere)
Unhappily.
It was ”somebody else’s problem.”
We couldn’t do what we wanted.
(Continuous deployment!)
Complexity was killing our projects.
We started caring.
We decided to cut the Gordian knot.
Our solution:
Standard Java Maven Jetty
Let’s see how it works.
Example application
Can’t change parent!
A developer has released a new version (1.0.1).
Create a test environment and try it out.
Server: Initial installation
./install.sh 1.0.1
Preproduction environment
Server: Upgrade production
Production environment
Lesson learned: Package everything you need.
Experience: Separate information that should survive upgrades.
Symbolic link
port=9090 shutdown_port=9190 datasource.url=jdbc:mysql://localhost/nocontainer_dev datasource.username=johannes datasource.password=johannes
New requirement: Delete
Testing: Embedded integration test
public void testDeleteCategory() throws Exception { // Insert test data Category category = new Category(uniqueName()); Serializable key = getRepo().insert(category); getRepo().writeChanges(); // Ensure that data is displayed in index beginAt("categories/list.html"); assertTextPresent("Showing all categories"); assertLinkPresentWithText(category.getName()); // Delete it beginAt("categories/edit.html?id=" + key); submit("delete"); // Ensure that after delete, we go to index assertTextPresent("Showing all categories"); // Ensure that the category is no longer there assertLinkNotPresentWithText(category.getName()); }
public void testDeleteCategory() throws Exception { // Insert test data Category category = new Category(uniqueName()); Serializable key = getRepo().insert(category); getRepo().writeChanges(); // Ensure that data is displayed in index beginAt("categories/list.html"); assertTextPresent("Showing all categories"); assertLinkPresentWithText(category.getName()); // Delete it // Ensure that the category is no longer there assertLinkNotPresentWithText(category.getName()); }
public void testDeleteCategory() throws Exception { // Insert test data // Ensure that data is displayed in index // Delete it beginAt("categories/edit.html?id=" + key); submit("delete"); // Ensure that after delete, we go to index assertTextPresent("Showing all categories"); // Ensure that the category is no longer there }
public void testDeleteCategory() throws Exception { // Insert test data // Ensure that data is displayed in index // Delete it beginAt("categories/edit.html?id=" + key); submit("delete"); // Ensure that after delete, we go to index assertTextPresent("Showing all categories"); // Ensure that the category is no longer there }
${category.name} #showErrors("category") Parent: #selectForObject("category.parent" $allCategories) Description: #springFormInput("category.description" "") #springShowErrors(" " "") Type: #selectForEnum("category.type" $categoryTypes) List all
${category.name} #showErrors("category") Parent: #selectForObject("category.parent" $allCategories) Description: #springFormInput("category.description" "") #springShowErrors(" " "") Type: #selectForEnum("category.type" $categoryTypes) List all
public void testDeleteCategory() throws Exception { // Insert test data // Ensure that data is displayed in index // Delete it beginAt("categories/edit.html?id=" + key); submit("delete"); // Ensure that after delete, we go to index assertTextPresent("Showing all categories"); // Ensure that the category is no longer there }
Debugging: Just like a main method
protected ModelAndView onSubmit( HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) { Category category = (Category)command; if (category.getId() == null) { Serializable key = repository.insert(category); return new ModelAndView(new RedirectView("show.html?id=" + key)); } repository.update(category); // Redirect on post - go back to the same page return new ModelAndView( new RedirectView(req.getRequestURI() + "?" + req.getQueryString())); }
protected ModelAndView onSubmit( HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) { Category category = (Category)command; if (category.getId() == null) { Serializable key = repository.insert(category); return new ModelAndView(new RedirectView("show.html?id=" + key)); } if (req.getParameter("delete") != null) { repository.delete(Category.class, category.getId()); return new ModelAndView(new RedirectView("list.html")); } repository.update(category); // Redirect on post - go back to the same page return new ModelAndView( new RedirectView(req.getRequestURI() + "?" + req.getQueryString())); }
Continuous integration
(Not demoed)
mvn deploy
Continuous Deployment
The Simplest Thing...
... cron
Mon Sep 10 18:37:02 EDT 2007: Not upgraded, skipping restart Mon Sep 10 18:38:13 EDT 2007: Installed new version Stopping Starting Testing connection (user=johannes, url=jdbc:mysql://localhost/nocontainer_dev)... OK! Deploy 'nocontainer' from /home/nocontainer/test/nocontainer- app/curr/repo/com/b rodwall/nocontainer/nocontainer-web/1.1- SNAPSHOT/nocontainer-web-1.1-SNAPSHOT.wa r Server started on in s Mon Sep 10 18:39:02 EDT 2007: Not upgraded, skipping restart
Experience: Look like a UNIX service
./prod/nocontainer-app/operate.sh start./prod/nocontainer-app/operate.sh stop./prod/nocontainer-app/operate.sh status
Release: mvn release
Upgrading staging server to 1.0.2
Upgrading production server to 1.0.2
Summary
1.Add a test
2. Make it pass
3. Check it in
4. Continuous integration and deployment
5. Run it at test server
6. Deploy to staging
6. Final verifications
7. Deploy to production
Results:
1. Better management
2. Simplicity
3. Isolation
4. Better development support
1: Single commands to...
... Build
... Install
... Upgrade
=> Ease of deployment
2: A simpler environment
... No dependencies between servers
... We own the Main class
Scripted server acts as UNIX service.
Upgrade with ssh, cron
Avoid EJBs, JMS, XA
3: Better isolation between applications
(especially: JVM version)
The ultimate irony:
JPA (EJB 3) via Hibernate on Java SE > 1.5
Our WebSphere servers still don’t support 1.5
Avoid Java EE server to get Java EE features
4: Better development attributes
Embedded integration tests
Easier to debug on server
Exact same software in debugger and server
Same versions of all libraries
Free, low footprint installation on workstation
Challenges:
How ready is Jetty for production?
What is being run in the wild?
Jetty: 221k
(Okay, then)
Load balancing and failover
(No state in application server)
Two week intensive stress testing
We could not make it fail
Organization: Open source procurement process
Organization: Showing this to the WebSphere guys
Support: Per project
Application management shortcomings
”On what servers is this application running?”
”What applications are running on this server”
Making: ”jettyctl”
Requires some documentation
Don’t go overboard with management
YAGNI
Why Anti-pattern
Insufficent isolation means risk
Insufficient isolation means older versions
Outside-in means lack of control
Outside-in means lack of understanding
Outside-in means complexity
Deployment apps means manual work
Conclusion:
In Soviet Russia, Application Server contains you!
We have turned the app server inside out
We choose Jetty, Maven, mod_proxy
These worked for us
(You may prefer glassfish, webstone or whatever)
(Shameless plug)
Q&A