Tuesday, September 14, 2010

Scalable Site Structure

If you've been developing web sites for a few years, you've probably developed a lot of sites with a structure similar to the following:

  • /css/
  • /images/
  • /scripts/
  • index.php
  • about.php
  • products.php

One of my colleagues likes to keep the root directory as clean as possible, so his site structures look like this:

  • /content/about.php
  • /content/products.php
  • /common/
  • /images/
  • index.php

Which I find slightly irritating because I prefer to keep my CSS separate from my JavaScript, and the /content/ folder is just taking the mess from one place and moving it to another.

Of course, the Holy Grail of site structures is to have clean URLs like this:

  • /about/
  • /css/
  • /images/
  • /products/
  • /scripts/
  • index.php

If you have a dynamic site, this is easily achieved using mod_rewrite, so /about/ becomes index.php?page=about, but sometimes the client doesn't want to pay for a dynamic site. In that case we use the Poor Man's Clean URLs™ wherein we actually create the /about/ and /products/ folders and put an index.php file in each with the static content. The PHP is then only really used to include a header, footer, navigation, etc.

As a site grows, performance and scalability become a (greater) concern. Popular site speed analysis tools often recommend using a content distribution network. Whether you outsource this or try to do it in-house, it sounds like a lot of work.

I hadn't had the privilege of working on a site that experienced enough traffic to be concerned about scalability until earlier this year. The company was running a national television commercial. They had hired another company to develop a micro-site on a separate server for the campaign, but viewers were being directed to the regular corporate site where a gigantic ad enticed them to visit the micro-site. If you're shaking your head right now, me too. The corporate web server was going down almost daily.

The web statistics package indicated that the most downloaded files were the product videos. I still don't know why they aren't using YouTube or Vimeo for this purpose. On a hunch, I copied the video files to the micro-site's web server and changed all the links. They experienced zero downtime after that.

This experience made me think about how to build scalability into a site from the beginning. Something in the back of my mind twigged a memory of reading something about serving static content from a cookieless domain. Cookies aside, it turns out that serving content from multiple subdomains has certain performance advantages.

Any discussion of clean URLs would not be complete without mentioning www.example.com vs. example.com. Dropping the www seems to be the "cool" thing to do, but my personal feeling is that www.example.com is more descriptive of what you will find there. The mail server is mail.example.com, so the web server should be www.example.com.

So here's the site structure I'm planning to use for future projects:

  • /css/ - css.example.com
  • /images/ - images.example.com
  • /images/logos/ - images.example.com/logos/
  • /images/products/ - images.example.com/products/
  • /js/ - js.example.com
  • /www/ - www.example.com
  • /www/about/ - www.example.com/about/
  • /www/products/ - www.example.com/products/
  • /www/index.php - the actual home page
  • /index.php - file which redirects to www.example.com

The subdomains can be hosted from the primary web server while traffic is small, and migrated to dedicated servers when the traffic increases. The URLs to the images, scripts, and stylesheets don't change, so the PHP files don't need to be updated. Use plenty of folders to keep things organized. You can see I made folders at images.example.com to keep the logos separate from product pictures. If there will be a lot of pictures for each product, I would make folders within the products folder to keep them segregated.

Sunday, September 5, 2010

Roman Numerals, Part 4

Keith Alexander of Albuquerque, New Mexico writes:
“I like your Roman Numeral library. I needed a function to test for Roman numerals, so I wrote this one.
  1. // Check to see if the string is a Roman Numeral
  2. // NOTE: this doesn't check for fractions, overbars, the Bede "N" (zero) etc.
  3. // NOTE: It also doesn't check for a well-formed Roman Numeral.
  4. function is_roman_numeral( $roman )
  5. {
  6.     // Strip every non-word character
  7.     // - A-Z, 0-9, apostrophe and understcore are what's left
  8.     $roman = preg_replace( "/[^A-Z0-9_']/iu", "", $roman );
  9.     // if it contains anything other than MDCLXVI, then it's not a Roman Numeral
  10.     $result = preg_match( "/[^MDCLXVI]/u", $roman );
  11.     if( $result )
  12.     {
  13.         return FALSE;
  14.     }
  15.     return TRUE;
  16. }

Who knows if blogger is going to show it properly. If not, just contact me and I'll send it you in email or something. Anyway, it's something I wrote in 5 minutes. If you want to add it to your library, modified or otherwise, please feel free.”

Thanks for writing in Keith, and sorry for the late response. There are two ways to validate a Roman number, using regular expressions like you did, and converting back to an Arabic number (if the conversion fails, it's not a Roman number).

I'm not sure about using a regular expression to remove non-word characters. My gut tells me that anything containing such characters should fail validation as a Roman number. Also, I would reverse the match and eliminate the if statement by directly returning the result of the match.

PHP

  1. function isRoman($roman)
  2. {
  3.     return preg_match("/[MDCLXVI]/u", $roman);
  4. }

ASP

  1. function isRoman(roman)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .IgnoreCase = true
  6.         .Global = true
  7.         .Pattern = "[MDCLXVI]"
  8.     end with
  9.     if regEx.Test(roman) then
  10.         isRoman = true
  11.     else
  12.         isRoman = false
  13.     end if
  14.     set regEx = nothing
  15. end function


Calculating annual salary for employees paid hourly

Last time I explained how to calculate the annual salary for an employee who is paid by the hour. Today I'm going to show you by writing a function for it, and we'll write one for monthly salary too.

  1. function annualSalary(someYear, hourlyPay)
  2.     const hoursPerDay = 8
  3.     annualSalary = 0
  4.     for i = 1 to 12
  5.         annualSalary = annualSalary + WorkingDays(someYear, i) * hoursPerDay * hourlyPay
  6.     next
  7. end function
  8. function monthlySalary(someYear, someMonth, hourlyPay)
  9.     const hoursPerDay = 8
  10.     monthlySalary = 0
  11.     monthlySalary = monthlySalary + WorkingDays(someYear, someMonth) * hoursPerDay * hourlyPay
  12. end function


View ASP implementation on Snipplr

Wednesday, September 1, 2010

Number of working days in a month

I didn't realize it's been over a year since my last post. Most of the code I've written this past year has been too specialized to be considered useful to anyone outside of my present employer, but this week I was presented with a problem with our Intranet application where the annual salary of employees who are paid hourly was not being calculated correctly. I'm working with the application vendor to get this corrected in the next version, and learned some interesting things about how to calculate this correctly.

A common formula to calculate annual salary for hourly employees is hourly pay × 40 hours/week × 52 weeks/year. This is very close to accurate, but 365/366 days per year does not divide evenly by 7 days/week.

A more accurate result can be obtained by multiplying the hourly pay by the number of working hours in a year. One way to calculate this is based on the last day of the year. In a non-leap year, there are 2080 working hours if the last day of the year is a Saturday or Sunday, and 2088 working hours if the last day of the year is a weekday. In the case of a leap year, there are 2080 working hours if the last day of the year is a Sunday, 2088 working hours if the last day of the year is a Saturday or Monday, and 2096 working hours for any other weekday.

At our company, we work 8 hours each day, so we can calculate the number of working days rather than the number of working hours and multiply by 8 hours/day. Since the number of days per month is variable, I decided to calculate working days per month separately and sum them up for working days per year.


  1. Function WorkingDays(someYear, someMonth)
  2.     WorkingDays = 0
  3.     For i = 1 To MonthDays(someYear, someMonth)
  4.         If Weekday(DateSerial(someYear, someMonth, i)) <> 1 And Weekday(DateSerial(someYear, someMonth, i)) <> 7 Then
  5.             WorkingDays = WorkingDays + 1
  6.         End If
  7.     Next
  8. End Function

You can take the result and multiply by 8 hours/day to get the number of working hours in a month, and then multiply by the hourly pay to get the monthly salary. The annual working days can be obtained by calling the function in a for i = 1 to 12...next loop, multiplied by 8 hours/day to get the number of working hours in a year, and then multiplied by the hourly pay to get the annual salary.

We ignore holidays and vacation days since the employee is paid for those as well, though technically vacation pay is accured at 4% so you could subtract vacation days (probably either 10 or 15, depending on number of years of service) and multiply by 1.04 to get an even more accurate amount for annual salary.


View ASP implementation on Snipplr.