Wednesday, February 29, 2012

Test Driven Development in Thai

Test Driven Development หรือ TDD คือระเบียบวินัยการออกแบบซอฟต์แวร์ โดยไม่เริ่มจากการออกแบบโคด แต่เริ่มด้วยการเขียนบททดสอบ นัยหนึ่ง การทดสอบก็ถือได้ว่าเป็นการออกแบบโคดนั่นเอง แต่เป็นการออกแบบที่สามารถนำมาใช้ยืนยันพฤติกรรมของซอฟต์แวร์ได้จริงโดยอัตโนมัติ

Testing มีจุดประสงค์ก็คือ
1) เป็นการกำหนดสเปคของโคดที่เราจะเขียน ว่าเราต้องการให้มันมีกลไกอย่างหนึ่ง
2) ยืนยันว่ากลไกที่เราเขียนออกมานั้นถูกต้อง
3) ป้องกันโคดเดิม ไม่ให้โคดใหม่มาเปลี่ยนกลไกโดยที่เราไม่ได้ตั้งใจ
4) เป็นหนังสืออ้างอิง สำหรับเพื่อนร่วมงานหรือลูกค้าที่จะมีความจำเป็นจะต้องศึกษาระบบเพื่อรับช่วงต่อ

เพื่อให้บรรลุจุดประสงค์เหล่านี้ ก็เลยมีระเบียบวินัยบางประการครับ ระเบียบวินัยเหล่านี้ก็คือ
1) เทสต้องแยกเป็นหน่วยย่อย เพื่อกำหนดวัตถุประสงค์ที่เป็นหน่วยเล็กที่สุดที่สมเหตุสมผล
2) เมื่อพร้อมจะคอมมิทงานแต่ละครั้ง เทสทุกเทสจะต้องผ่านหมด
3) ถ้างานที่คอมมิทไปทำให้เทสอื่นไม่ผ่าน ทั้งทีมต้องไม่คอมมิท จนกว่าคนที่คอมมิทข้อผิดพลาดจะแก้ไขเสร็จ และคอมมิทงานที่ซ่อมแซมแล้ว มิฉะนั้นข้อผิดพลาดอาจมีงานอื่นมาซ้อนทับ ทำให้การแก้ไขไม่มีที่สิ้นสุด
4) เทสต้องอ่านง่าย และสมดุลกับโคด ไม่ยืดเยื้อ แต่ก็ครอบคลุมกลไกครบถ้วน
5) สามารถทำซ้ำได้ทุกครั้งโดยที่ผลลัพธ์ต้องเหมือนเดิมทุกครั้งหากไม่มีความเปลี่ยนแปลงใดๆ เกิดขึ้นกับโคดหรือบททดสอบ

เมื่อเรามีวัตถุประสงค์ กับระเบียบวินัยดังนี้ การปฏิบัติก็มีหลักการชัดเจนขึ้น เราก็สามารถออกแบบระบบการทดสอบได้ดังนี้

กำหนดขอบเขต ตามระดับความครอบคลุม เราสามารถแบ่งการทดสอบเป็นระดับ ลดหล่นลงมาได้

จากระดับสูงสุดคืองานทดสอบเพื่อยอมรับการส่งมอบงาน (acceptance testing) โดยการทดสอบแต่ละหน่วย จะต้องบรรลุถึงจุดประสงค์ทางธุรกิจ (business value) แต่ละข้อที่กำหนดไว้ เช่น
1) สามารถลดเวลาการทำงานของฝ่ายสินเชื่อลงจากสามวันเป็นหนึ่งวัน
2) โดยเมื่อปฏิบัติตามขั้นตอนที่ออกแบบไว้จะสามารถดำเนินการตั้งแต่ลูกค้าส่งใบขอสินเชื่อจนถึงแจ้งลูกค้าถึงการอนุมัติ หรือปฏิเสธสินเชื่อดำเนินการไปอย่างราบรื่นโดยไม่มีข้อผิดพลาด
3) ฝ่ายการเงินต้องรับทราบข้อมูลจากฝ่ายสินเชื่อภายในเวลาที่เหมาะสมเพื่อพิจารณาแผนการปฏิบัติงานต่อไป โดยข้อมูลที่ต้องการประกอบด้วย […]

รองลงมาเป็นการทดสอบแบบ บุรณาการ (Integration Testing) เป็นการทดสอบระบบทั้งหมด ให้การทำงานเป็นไปตามความต้องการที่ออกแบบไว้ เช่น ทดสอบว่า
1) application ของเรา สามารถดึงข้อมูลจากธนาคารเพื่อตรวจสอบหลักทรัพย์ ก่อนอนุมัติเงินกู้
2) เมื่ออนุมัติเงินกู้แล้ว application ของเรา ส่งอีเมลถึงลูกค้า และส่งสัญญาณแจ้ง application ของฝ่ายการเงิน เพื่อแจ้งให้ทราบต่อไป

ระดับรองลงมา เป็นการทดสอบการใช้งานจริง ที่ไม่ครอบคลุมถึงปัจจัยภายนอก (Functional Testing) ระดับนี้จะครอบคลุมถึงการใช้งานจริงของ application ในระดับผู้ใช้ ลงมือใช้งาน application ของเรา ตัวอย่างเช่น
1) ถ้า log in ด้วยข้อมูลที่ถูกต้อง หน้าจอจะแสดงหน้าหลัก บอกชื่อ และเวลาที่ผู้ใช้เข้าใช้งานครั้งสุดท้าย
2) เมื่อผู้ใช้กรอกแบบฟอร์มครบถ้วนและกดตกลง application จะเรียกใช้ server แบบ AJAX, แสดง progress bar เพื่อแสดงความคืบหน้าในการดำเนินการ เมื่อดำเนินการเสร็จแล้ว จะมีบันทึกการขอสินเชื่อของผู้ใช้
3) เมื่อสินเชื่ออนุมัติ และผู้ใช้เข้าใช้งาน จะมีกรอบข้อความแจ้งเตือนถึงการอนุมัตินี้

เมื่อเราแยกย่อยลงมาอีก ก็จะเป็น การทดสอบหน่วยการทำงาน (Unit Testing) ระดับนี้จะลงรายละเอียดของออบเจคต์แต่ละส่วน เมธอดแต่ละส่วน ว่ามีกลไกเป็นอย่างไร ตัวอย่างเช่น
1) ออบเจคต์สินเชื่อ ต้องมีความสัมพันธ์กับออบเจคต์ผู้ใช้ 1 คนเท่านั้น ไม่สามารถมีสินเชื่อ ที่ไม่มีผู้ใช้อยู่ในระบบได้
2) เมธอด อนุมัติ มีหน้าที่สามประการ
2.1) ส่งอีเมลแจ้งลูกค้า
2.2) เรียก web service ของฝ่ายการเงิน
2.3) บันทึกข้อมูลข่าวสาร เพื่อแจ้งให้ลูกค้าทราบผ่านทางหน้าเวบ
3) วิธีการเรียก web service ของฝ่ายการเงิน
3.1) ใช้ RESTful protocol โดยมี URL คือ POST http://example.com/mortgages/approved
3.2) body ของ request เข้ารหัสด้วย JSON และมีโครงสร้างดังนี้ {…}
3.3) เมื่อฝ่ายการเงินได้รับข้อความแล้ว จะตอบกลับมาด้วย Header 200
3.4) หากมีข้อผิดพลาด ฝ่ายการเงินจะตอบกลับมาด้วย Header 403 และใน body จะมีออบเจคต์เข้ารหัสด้วย JSON และมีโครงสร้างดังนี้ {…}

เมื่อเรากำหนดขั้นตอนการทำงานได้ดังนี้แล้ว เราสามารถทำได้สองทิศทางคือ บนลงล่าง หรือล่างขึ้นบน แล้วแต่ความสมเหตุสมผลงานงานหน่วยนั้นๆ

การทดสอบแบบล่างขึ้นบน คือการเริ่มจาก Unit testing ก่อน ออกแบบออบเจคต์ของเราให้มีกลไกที่เรียบง่ายที่สุดเพื่อที่จะส่งเสริมให้เราสามารถออกแบบ tests ที่ระดับสูงขึ้นได้ จากนั้นค่อยยกระดับการทดสอบขึ้น เป็น functional แล้วค่อยเป็น integration

การทดสอบแบบบนลงล่าง ก็เริ่มจากระดับบนสุดก่อน ถ้ามีปัจจัยภายนอก ก็เริ่มที่ integration แล้วเมื่อเห็นความต้องการว่ามีความจำเป็นจะต้องมี function ดังนี้ๆ, ก็เริ่มเขียน functional ตามความจำเป็น แล้วก็ย่อยลงเป็น unit test ตามความจำเป็นเช่นกัน

ทั้งสองแบบมีข้อดีข้อเสียด้วยตัวมันเอง การทดสอบแบบล่างขึ้นบน เหมาะสำหรับระบบใหม่ ที่ยังไม่มีข้อจำกัดใดๆ ทำให้ระบบภายในเรียบง่ายและชัดเจน แต่มีความเสี่ยงที่จะออกแบบล่วงหน้ามากเกินไป หรือไม่ตรงตามความจำเป็นที่เกิดขึ้นเมื่อทดสอบในระดับที่สูงขึ้น

ส่วนการทดสอบแบบบนลงล่าง เหมาะสำหรับระบบที่มีปัจจัยภายนอกที่ชัดเจน เช่น ข้อกำหนดของ web service, หรือหน้า User Interface และ workflow ที่ชัดเจน ทำให้เราสามารถกำหนดขอบเขตชัดเจนว่าความจำเป็นของชิ้นส่วนต่างๆ เกิดจากความจำเป็นจริงๆ แต่การออกแบบเพื่อให้เข้ากับระบบที่มีอยู่เดิม จะทำได้ยากกว่าเพราะการทดสอบระดับสูงคำนึงถึงกลไกเบื้องล่างน้อยกว่ามาก

การทดสอบที่ไม่สมเหตุสมผลคือการทดสอบที่ไม่ต่อเนื่อง ไม่สอดคล้องกับข้อกำหนดในเนื้องาน และการทดสอบระบบที่เราไม่มีหน้าที่และความรับผิดชอบ เช่น library จากบุคคลที่สาม การทดสอบที่ผิดพลาด การทดสอบที่ไม่ครอบคลุมกลไกอย่างครบถ้วน สิ่งเหล่านี้ทำให้การทดสอบเชื่อถือไม่ได้ เมื่อเชื่อถือไม่ได้ ก็ไม่มีประโยชน์ ก็จะไม่มีการบำรุงรักษา เป็นวงจรที่ขยายวงกว้างอย่างรวดเร็ว ทำให้งานทั้งระบบ ขาดข้อยืนยันว่าถูกต้อง ความน่าเชื่อถือ และเอกสารอ้างอิง ในที่สุดก็จะไม่สามารถใช้งานได้อย่างยั่งยืนต่อไป

ส่วนประกอบของการทดสอบในทางเทคนิคมีสามประการหลักๆ คือ
1) สถานะเริ่มต้น (setup)
2) เรียกใช้กลไกที่ต้องการทดสอบ (performance)
3) รับรองว่าสถานะที่เปลี่ยนไป สอดคล้องกับพฤติกรรมที่เราต้องการจากกลไกนั้น (assertion)

การกำหนดสถานะเริ่มต้นทำให้เราทราบสถานะที่แน่นอนของระบบในขณะนั้น เราจะต้องทำให้ไม่มีปัจจัยภายนอกใดๆ ที่ทำให้มีความไม่แน่นอนส่งผลกับกลไกที่กำลังทดสอบนั้น

การเรียกใช้กลไกที่จะออกแบบ ขึ้นอยู่กับระบบ แต่ต้องเรียกใช้ในส่วนที่เฉพาะเจาะจงที่สุดที่สมเหตุสมผล เช่น เรียกใช้เมธอดนั้นโดยเฉพาะในการทดสอบ unit testing หรือการเรียก controller action โดยตรงในการทดสอบ functional testing หรือการเลือกคลิกปุ่มเฉพาะเจาะจงในการทดสอบ integration testing เป็นต้น

การรับรองสถานะ ควรทำให้เฉพาะเจาะจงแยกเป็นแต่ละกรณีไป เช่นเมื่อเรียกเมธอดเดียวกัน อาจต้องมีการรับรองสามอย่าง ให้แยกทดสอบทีละอย่าง โดยสามารถออกแบบให้การสร้างสถานะเริ่มต้นและการเรียกใช้งานกลไก เป็นเมธอดที่สามารถเรียกซ้ำได้

ข้อสำคัญที่ต้องระวัง คือสถานะผลลัพธ์จากการทดสอบหนึ่ง อาจส่งผลกระทบกับการทดสองอีกชุดหนึ่ง ซึ่งข้อผิดพลาดนี้จะไม่แสดงให้เห็นชัดเจน บางครั้งกว่าจะตรวจพบ ก็เมื่อได้ใช้งานจริงแล้ว

คำถามที่น่าสนใจคือ แล้วจะทราบได้อย่างไร ว่าการทดสอบที่เราเขียนนั้นถูกต้อง แล้วเราจะทดสอบการทดสอบการทดสอบโดยไม่มีที่สิ้นสุด หรืออย่างไร? คำตอบคือ โคดของเรา จะเป็นตัวทดสอบบททดสอบด้วยเช่นกัน หากการทดสอบผิดพลาด แต่เราทบทวนจนแน่ใจแล้วว่าโคดของเรา สมเหตุสมผลสามารถยืนยันได้ด้วยทฤษฏีหรือวิธีอื่นๆ เราก็สามารถตั้งข้อสงสัยได้ว่าการทดสอบของเราผิดหรือไม่

เมื่อเริ่มเขียนบททดสอบ ต้องเขียนให้แน่ใจว่าการทดสอบนี้ ต้องแจ้งให้เราทราบว่าโคดมีข้อผิดพลาด ก่อนที่เราจะเริ่มเขียนโคด มิฉะนั้นจะไม่มีทางทราบว่าโคดที่เราเขียนใหม่นี้ เป็นส่วนที่ทำให้การทดสอบของเราผ่านจริง (red bar)

จากนั้นจึงค่อยเริ่มเขียนโคด ให้ง่ายที่สุด ที่สมเหตุสมผล และทำให้การทดสอบของเราผ่าน (green bar)

จากนั้นค่อยพิจารณา ว่าโคดของเราสามารถปรับปรุงให้ดีขึ้นได้หรือไม่ สามารถ refactor ให้ไม่ซับซ้อน อ่านง่าย เข้าใจง่าย มีประสิทธิภาพ และไม่ซ้ำซ้อนกับกลไกอื่นได้หรือไม่ ทั้งนี้เรามีหลักประกันจากการทดสอบ ว่าพฤติกรรมของโคดจะต้องไม่เปลี่ยนแปลงในช่วง refactor นี้

จากนั้นก็ทำเป็นวงจรต่อเนื่องไปเรื่อยๆ red bar, green bar, refactor จนกว่าจะบรรลุวัตถุประสงค์ของโปรแกรมตามต้องการ

จะเห็นได้ว่า ระเบียบวินัยแบบ TDD นี้ สามารถนำไปใช้กับซอฟต์แวร์ทุกระดับ ตั้งแต่เวบเล็กๆ ไปจนถึงระบบ ERP ขององค์กรระดับชาติ ทำให้ซอฟต์แวร์มีความน่าเชื่อถือ ตรงตามความต้องการ ไม่ขาดไม่เกิน บำรุงรักษาได้ มีเอกสารอ้างอิงชัดเจน และเป็นการบังคับให้เปลี่ยนเอกสารอ้างอิงทุกครั้งที่พฤติกรรมของซอฟต์แวร์เปลี่ยนไปจากเดิม ทำให้เอกสารอ้างอิงนี้ใช้ประโยชน์ได้จริงอยู่เสมอ

Thursday, February 23, 2012

Integrating simile timeline with Rails on asset pipeline

SIMILE Widgets from MIT has a few neat UI widgets available to use. They are quite powerful and scalable. The one I happen to have a need for is the Timeline. It provides perceptively infinite timeline widget in the scale that can range from years to minutes, intermixing on demand. The tool comes with detailed wiki that can help you set up a timeline on your html page elegantly. It can be themed and ajax'd. Events can be handled gracefully. Best of all: no flash. The copyright notice says 2006-2009, long before Rails asset pipeline was invented. There's not much information on the web about how we can utilize SIMILE Timeline in a proper manner with the asset pipeline, so here it is. Keep in mind that I put both timeline_ajax and timeline_js directories in vendor/assets.


  1. Make sure the timeline_ajax and timeline_js directories are in the asset path:
  2. application.rb
      config.assets.paths += path_to_timeline_ajax
      config.assets.paths += path_to_timeline_js
  3. Make sure the asset precompile file list includes files in these two directories:
  4. production.rb
      precompile_list = %w(app lib vendor).map {|path|
        Dir[Rails.root.join(*%W(#{path} assets ** *))].select {|f|
          f =~ /(\.js|\.s?css)/
        }
      }.flatten.map {|f|
        f.split(File::SEPARATOR).last
      }.uniq
      config.assets.precompile = (config.assets.precompile + precompile_list).uniq
  5. Add the timeline configuration to the header of the view using timeline:
  6. application.html.erb
      ...
      <%= yield :head %>
      ...
    controller_name/show.html.erb
      <%= content_for(:head) do %>
        <%= javascript_tag do %>
          Timeline_ajax_url = "<%= asset_path("simile-ajax-api.js") %>";
          Timeline_urlPrefix = "/assets/";
          Timeline_parameters = 'bundle=true';
        <% end %>
      <% end %>
  7. Require timeline with Sprocket
  8. application.html.erb
      ...
      <%= yield :head %>
      <%= javascript_include_tag controller_name %>
    controller_name.js
      ...
      //= require timeline-api
      ...
  9. In timeline-api.js, make sure the url points to the asset directory
  10. timeline-api.js
      // Change:
      var url = useLocalResources ?
        "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
        "http://static.simile.mit.edu/ajax/api-2.2.0/simile-ajax-api.js";
      // To:
      // var url = useLocalResources ?
      //   "http://127.0.0.1:9999/ajax/api/simile-ajax-api.js?bundle=false" :
      //   "http://static.simile.mit.edu/ajax/api-2.2.0/simile-ajax-api.js";
      var url = "/assets/simile-ajax-api.js";
    
  11. Go forth and chronicle the world. Follow the instructions on SIMILE Timeline's wiki page.

Saturday, February 11, 2012

On javascript, and its testing

Lately I run into requirements that need an intensive javascript logic, including Ajax, jQueryUI and dynamic DOM manipulation logic. It makes me ponder over issues I have had with this whole thing. 

I hate it. 

Why? 

Javascript itself is a nice little language, completely object oriented, on prototyping paradigm. But when it comes to Ajax and DOM manipulation, well, there is no other term more appropriate than shit hits the fan. 

jQueryUI is a great tool. You select the DOM elements with the CSS3 selector, you attach things to it. Job's done. I can add a calendar widget, a date picker, and a rich text editor to my site in no more than a line of javascript code each. The hard part, though, begins when I have to begin handling the events these widgets fire. 

When I want to handle a hover event on a calendar entry, which element am I talking to? There is 'this' variable which is the reference to the object firing the event. But I want the event to affect other elements of the same kind. Dig through the 5000 lines of library code (which is not the cleanest code in the world, and no test to refer to either) is not an easy task. How is jQueryUI object defined? How do I access the methods inside the object? It is either my lack of understanding of the prototyping OO, or jQuery was not designed with the OO in mind. I suspect it's the former. 

Stuff like this reminds me how valuable it is to stick to Test-Driven-Development. The test lets you make sure that new code you add in will not break original functionality. The new test lets you make sure you are coding to the requirement, nothing more. It assures you that any refactoring you do maintains the same functionality and purpose, and nothing more. 

So how do we go about writing tests for these Javascript code? 

Well, not easy. The one word you need to keep reminding yourself: discipline. A strict one.

First let me explain on the technical side how javascript testing is done. There are two ways you can test javascript code. Standalone javascript unit testing, and high level functional testing.

Standalone javascript unit testing is a fine grain level of testing, focusing on the javascript code itself, and nothing else. The tool available is Jasmine. Jasmine runs anywhere javascript code can run. V8, Rubyracer, Rhino, Firefox, Chrome, IE, Opera, etc. All you need to do is to make the Jasmine standalone package accessible in the scope of the code, and you can harness all the testing facility it provides. To test the DOM manipulation logic, you need HTML fixture. The fixture needs to have the DOM you want to manipulate, and all the condition you expect from the 'real' page. When the test is run, the assertion should be able to assert that the DOM with the particular CSS matching condition should change in the way you have expected. 

You should see a problem by now. Do you? 

The problem is, we create the 'real' page with the server side code, with dynamic content depending on the states of objects related. Unless you are on node.js code, you cannot effectively invoke those 'real' pages with the plain Jasmine standalone. The fixture, then, has to significantly match the 'real' page. Once the 'real' page changes, chances are you forget to update your fixture. The tests pass, but somehow there are bugs in the system. Also, as soon as you fall in the pit of 'we can just have the server side code generate the javascript for us,' the ability to test the javascript loses its coverage. If that dynamic javascript includes crucial part of the logic, such as initialization variables, then the whole suite breaks down. As soon as you have Ajax code in the path of the testing logic, the suite breaks down. A particular problem related to running the javascript unit testing on check in time is automation. If you want to run it as a check in script, you need some javascript engine which is separated from the browser's engine. You have the same problem as the fixture problem. They are not the same engine, and they may be different in functionality. Rhino, for example, does not update the DOM element width and height properties without having to manually calling style update functions. You can manually fire up the browser and run the suite there. But if I get $20 each time we forget, I won't be running around offering my service to feed my wife and child.

There are merits to javascript unit testing, no doubt. The limitation, however, is significant. You need to be careful to make sure that the DOM manipulation logic is safe, static, and as isolated as possible.

On the other hand, we have the high level functional testing. 

In this approach, we have the browser adapter that takes macros to run automatically, then after the macro is performed, assertions are made on the resulting page. Selenium is the prime example of this kind of test. You start out with a Selenium adapter opening up a browser, most of the time Firefox. Tell it to visit a URL, may be enter some text in the login field, and password field, click the login button. Then the test suite asserts that the current URL of the window is the home page, with a particular DOM element saying "Welcome to my site, #{username}", etc. 

This approach, of course, overcome all the limitation we have in the other approach. We can rely on anything that results in the page of interest. There is no fixture to worry about. There are adapters to allow high level functionality on all kinds of programming language. The test suites themselves are written in any language of choice. 

Problem within this approach is all the steps you need to achieve just to be able to start testing. You need a server up and running, with everything setup according to the fixture specification, a user in the database, the date and the time for time-sensitive data, etc. At the end of every single testcase we have to reset the whole thing. The server process is separated from the test suite, as the suite runs on the client side, but contradicts itself and go ahead and resets the state of the app on the server side as well. Transactional fixture cannot be performed, and data cannot be rolled back instead of deleted. Performance is a major concern. The more we cover, the polynomially longer the suite takes to perform. If the test is badly designed, the degree of increasing complexity becomes exponential. Without enough coverage, there is no use in investing in functional suite. With high enough coverage, and a big enough codebase, the time to run the test is actually longer than it to be of any use. There's a balance we need to strike. 

We can run the functional suite at check in time, but you have to think about how many times you are checking in a day, and how much time is spent waiting. We can run it as a CI suite, but how much do we really care about it? A mysterious bug that ends up being a wrong testcase, or a change in a tiny DOM element id, or class, will remind you of a shepherd boy tale. Soon enough, you will lose interest in maintaining it. At that point it ceases to be useful. 

Now, back to our one word, discipline.

Problems we have with both approaches can be mitigated to a certain degree if the whole team accepts some discipline terms. When it comes to Javascript, it must be considered as a separated set of logic from the server side code. Do not rely on the server side to provide anything. It's irony that Javascript asynchronous behavior, which the client-server HTTP paradigm lacks, requires us to think about Javascript logic very differently from how we think about the web application client-server logic. Javascript logic fits much more to the .NET analogy than we anti-M$ monkeys dare admit. With that understanding in mind, we have a better clue of how the code executes, and how the logic are supposed to be tested. We need to design our Javascript logic so that the Ajax facility, the jQueryUI facility, and any facilities that is hard or impossible to unit test are contained, isolated, and tested by the high level functional suite. The rest can then be tested with the unit test for acceptable performance reasons. 

This discipline is not achievable overnight. Especially when you have existing code base that has javascript logic splattered all over the place, intermixing Ajax, DOM manipulation, and server side code. The amount of time and effort it takes to fix this, and the business value it brings make it really hard to justify the investment from the stakeholders. 

Either you do it right from the very start, or you need one hell of influence. 

Start with discipline, good things will then follow. 

Configuration Management with Rails

DevOps is apparently the hot new thing, as it should be. The ideal world of sysadmin having the ability to (test-driven-) develop the configuration management of all the machines in their responsibility is here, now. Combining that with the power of cloud computing, the world is moving toward the next age of computing. The true Cloud Age.

I am a Rails guy. It will stay that way at least for a few years to come. I have been deploying different Rails application on different projects. They are all automated, all in different mindset, goal, and methodologies. Recently I have a chance to have a complete control on deploying a new code to a new server for a startup company. The experience is priceless, and I have a giant leap of understanding of why DevOps is such an important step we have to take to achieve continuous delivery.

Let's see our case study. We have requirements that our application is to be deployed on an Ubuntu 11.04 64 bit server. There are, as are a few other projects, other apps hosted on it as well. There is only one server in this startup company, and we are not about to go spend the little money we have on another box. The server box already have Apache2, MySQL, and whatever else a PHP site needs to have a running PHP site. What I need done is to configure it so that Apache2 proxies a subdomain requests to Rails, with Unicorn as the Rails server.

There are a few tools available. There's Puppet. There's Chef. There's our plain old shell script. My past experience with Puppet is not bad, but could have been better. Remembering all that DSL is a little unnecessary when I already know Ruby and Chef uses Ruby as its DSL. So Chef is a reasonable tool.

Learning Chef is a pain. At a glance, the documentation is really detailed about how things are organized, what goes where. Dig a little further, you'll see that Opscode people have a whole lot of assumption that what they know in their heads are also in yours. A simple question like where/how attributes are supposed to be defined in the node definition JSON file is not easy to answer. Even a simpler question like how do I start is also quite a hard question. Especially when you are decidedly not paying for a hosted Chef solution.

Enough rants, back to the point. Since I don't have too large an infrastructure to take care of, and I cannot afford to have a separate server running, I choose to deploy the whole thing with chef-solo. I package the whole chef repository, ship it to the target machine, and run it standalone.

With Chef, you can abstract a server box as a node. You have a whole lot of recipes to choose from. You add these recipes to the node's run list. When run, Chef will look at all the attributes you specify, for example, what user and group unicorn should run as, what the name of your Rails application is, what the virtualhost name of the Rails application is, etc. Then it reads all the recipes in the Chef repository, defining services, and other facilities along the way. Then it looks at the node's run list. It figures out what recipe to run. The recipes themselves are the code that installs package, put the right files in the right places with the right owner, group, and permission. It can dynamically create the file with erb, too. Upon completion, you have a server with all the packages installed, and ready to go. No matter how many times you run the script on any number of different boxes, the end result is the same. People with little experience on configuration management will not believe how hard it is to achieve this idempotent property by hand.

When enough nodes are defined, you will begin to see a pattern. The run list can be aggregated into groups and if you need a certain feature, the whole group of recipes need to be included. Chef allows you to define this group of run list, and other attributes, as roles. So what you can do is define the roles such as database_master, database_slave, httpd, rails, etc. So any time you want to define a new node, you can pick out the roles you need without having to specify every single recipe you need all the time. Don't-Repeat-Yourself.

Of course, nobody can achieve everything in one go without mistake. We need a way to test this whole thing. We don't want to test it on our only server with another running application. VirtualBox saves the day for me. I setup a virtual machine, take a pristine snapshot before I run anything on it. Then I start deploying chef to it. Things breaks many times, as I am no expert on configuration management. All I need to do is revert the virtual machine to the pristine state, and I have a fresh fixture to test things again. There is also chef-spec that is supposed to let you test your Chef recipes as well. Unfortunately I cannot learn Chef in concurrent with any other thing at one time. So it goes to my lab list.

All is fine and dandy, I got two nodes defined, one is the staging VM which requires everything, Apache2, MySQL, Bundler, Bluepill, Rails, and Unicorn. The second is for the production server, with only Bundler, Rails, Bluepills, and Unicorn. Ok, great. Now I can deploy the code and start having a server to show people. ... therein lies a problem.

Traditionally, Rails has its own deployment automation tool, Capistrano. It does the second half of what Chef is supposed to do. It puts Rails code in the right place, with the right symlink, etc. It also installs the bundle, runs migrations, and fires up the (monitoring process, which in turn fires up the) server. It also allows you to define other deployment tasks to be done on any number of remote machines involved with this Rails code.

The conflict begins when you have a part of Chef recipe that needs Rails deployed, but you cannot deploy Rails before Chef completes, such as the monitoring tool configuration, logging facility, and so on. A bad solution is to let Chef finish half the way, and have Rails finish the other half. This works, but I don't like the fact that my configuration is now fragmented into two places. Rails itself should not have been involved in configuration management in the first place anyway.

My plan of action is then to have Chef take care of everything, including deploying Rails itself. I will have a new rails-deployment cookbook that will: check out Rails code, with the right git tag, to /tmp, and run bundle install and cap deploy from there. Ideally, I will want to have most of the configuration files, such as  deploy.rb, database.yml, etc, in erb templates and managed by Chef as well. The end result should allow me to just change the release tag in the rails role, run chef, and the new code should be deployed and ready to serve client requests.

When that is done, I will come back and tell you how it goes.