Running server specific conditional code
I’d like to be able to deploy the same code base to my live, staging, test, backup and development servers, however sometimes the code is server specific. For example, I only want my live server to go through the live payment gateway; my other servers should use the test payment gateway.
One solution would be to handle the differences in the package manager configuration (eg Metacello configuration) and load different classes depending on the server. I take this approach for some of my code, but especially for the payment gateway I’d like a “belt and braces” approach where the server can query itself to ensure that it’s using a class that’s appropriate for the server.
The solution is two-pronged:
- First ensure the server can identify itself.
- Query the server and match the identity against an appropriate class, which encapsulates the differences in behaviour between the servers.
Identifying the server
On my minimally configured servers hostname
resulted in the following:
$ hostname
ip-10-234-189-50
The first task is to ensure hostname
identifies the server in a useful form. Unfortunately Centos and Ubuntu (10.4) based servers handle this differently:
Centos
Test to see if the server’s hostname is correctly configured:
$ hostname
ip-10-234-189-50
In this case it isn’t so let’s config it:
$ sudo vim /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=**getitmade.com**
.
.
.
$ sudo vim /etc/hosts
127.0.0.1 localhost localhost.localdomain **getitmade.com**
.
.
.
$ sudo hostname getitmade.com
$ sudo /etc/init.d/network restart
Testing the changes have worked:
$ hostname
getitmade.com
Ubuntu
Test to see if the server’s hostname is correctly configured:
$ hostname
ip-10-234-189-50
…not in this case; let’s configure it:
$ sudo vim /etc/hostname
staging.getitmade.com
$ sudo vim /etc/hosts
127.0.0.1 localhost localhost.localdomain **staging.getitmade.com**
.
.
.
$ sudo hostname staging.getitmade.com
$ sudo /etc/init.d/network restart
$ sudo restart hostname
Testing the changes have worked:
$ hostname
staging.getitmade.com
Resolving the class from the server
Now the servers are correctly configured, identifying the server from Smalltalk is trivially simple:
NetNameResolver localHostName
I chose to wrap the call to #localHostName
in a method which maps the server’s hostname string to a symbol and caches the result:
server
^ server ifNil: [
| servers |
servers := Dictionary new
at: 'getitmade.com' put: #live;
at: 'staging.getitmade.com' put: #staging;
at: 'testing.getitmade.com' put: #testing;
at: 'backup.getitmade.com' put: #backup;
yourself.
server := servers at: self serverName ifAbsent: [ #development ] ]
I’m particular interested in whether the server is live or not, so I encapsulate this as:
isLiveServer
^ self server = #live
Finally I abstract the differences between the servers in a class hierarchy and use a factory in the base class to return an appropriate class for the server:
merchantClass
| serverIdentification |
serverIdentification := IZServerIdentification default.
self allSubclassesDo: [:aMerchantClass |
(aMerchantClass designedFor: serverIdentification)
ifTrue: [ ^ aMerchantClass ] ].
^ nil
the #designedFor:
method is implemented on the class which encapsulations the parameters for live server as:
designedFor: serverIdentification
^ serverIdentification isLiveServer
Note: NetNameResolver>>#localHostName
on Pharo doesn’t work reliably on Mac OSX. See this message thread for the background to the issue. In my case, although I develop on a Mac, I deploy on Gemstone and Linux and so circumvent this problem. Also note NetNameResolver>>#localHostName
on Gemstone is a thin wrapper around:
GsSocket getLocalHostName