StyleWorks - Premium Photoshop Styles
This past March, I decided it was time to put my skills as a Drupal developer to use and launch a new online business. I knew early on that I wanted this business to be product-based, and after several weeks of playing with different ideas, I settled on selling premium Photoshop layer styles. It was the perfect opportunity to combine my love of photography and Photoshop with my passion for web development and Drupal.

Several months of product development later, StyleWorks was born. The site runs on Drupal 6, and integrates with FastSpring for e-commerce capabilities.

Designing the site: To Zen or not to Zen?

After iterating through several hundred designs in Photoshop, I finally had the look I wanted to go with, and it was time to make it come alive in Drupal. But first, a key decision had to be made: Start from scratch, or go with Zen?

Traditionally, I've always built my designs from scratch. I'm somewhat of a optimization junkie, and I love writing really lean code (I used to be a little more extreme about this than I am today. Back when I wrote DrupalModules.com, I avoided using things like CCK, favoring instead to write my own custom modules). So when I first looked at the Zen theme several years ago, my initial impression was "ugh, look at all this extra code! Why are there so many style sheets?" *Uninstall*

Since then, Zen has grown wildly popular, becoming the #1 theme on Drupal.org. I had always attributed this popularity to Drupal's growing audience of non-developers, but after hearing some positive reviews at a recent Drupal developer meet-up, I decided to give Zen another try.

The verdict? Yes, it does have a huge number of style sheets. And yes there is a ton of code. But it actually works pretty well. The CSS is flexible and well designed, and it managed to stay cross-browser compatible even after extensive tweaking.

What do I mean by flexible? Here's an example:

When I decided mid-project to change from a one-column layout to a two-column layout, I didn't need to rewrite any code, Zen took care of it. And later, when I decided to throw out my first design and give the site a completely different look, I was able to implement the changes with only a few edits to the underlying code.

Clearly, I had underestimated Zen.

Zen's flexibility, however, does come with a few tradeoffs.

If you're used to putting everything in one long CSS file, Zen's barrage of 30+ style sheets can be a little overwhelming at first. There are separate CSS files for almost everything (backgrounds, pages, tabs, forms, fields, etc). Fortunately, most of these files rarely need editing, so the actual number of style sheets you'll have open at any one time is closer to six than 30.

Zen's strict separation of CSS rules feels a little inefficient to me. I'm not sure there's a real benefit to having a DIV's background and border rules split across two different files. With so many style sheets, it can be tricky keeping track of which rules are in which file (I found Firebug to be very helpful in this regard).

The flexible column design also has some drawbacks. If you're thinking about resizing the layout, be prepared for a little grade school math. You'll have to add and subtract values in several places to keep the negative margins working. It's explained fairly well in the code comments, but it's annoying to have to keep redoing it while you're experimenting with column sizes.

Despite these quirks (some of which come down to personal preference), I'd have to say that Zen turned out to be a good choice overall, and I'd consider using it again for my next project.

Layout Decisions

Since layer styles are a highly visual product, I needed a layout that could accommodate a large number of images while still leaving room for a product description. So, I decided to go with a simple two-column layout: product images on the left, descriptive text on the right.

The only question was how to get the text and the images to display in separate columns. I looked at a number of different layout solutions, including Panels and Composite Layout, but none of them really felt appropriate.

Ideally, I just wanted to use Zen's column system. One way to do that was to use blocks, but I didn't want to create a new block for every page on the site!

That's when I found a module called Nodes in Block.

Nodes in Block lets you create special blocks that dynamically display content based on the current path. For example, you can configure the same block to show either a poll or an advertisement, depending on where the user is on the site. Controlling what shows up in the block is done through per-node visibility settings (it's very similar to setting up block visibility).

So I created a content type for product images, and another content type for product descriptions, and used Nodes in Block to get them both on the same page (images in the content main area, descriptions in the sidebar via a block). It worked, but having a product split into two different nodes was less than ideal.

Then I had the clever idea of displaying the same node in both areas. All I needed to do was check the $node->nodesinblock variable in the node template, and I could control which fields would be output to which parts of the page.

Making the node display twice was accomplished by setting the node's "Nodes in Block" visibility to its own path.

nodes in block visibility

Node Structure

Thanks to the Nodes in Block solution, I was able to consolidate my product images and product descriptions into a single content type called a "product demo". The content type itself is fairly simple, relying on only text and file fields.

Text Fields:

  • Long Title
  • Short Title
  • Subtitle
  • Teaser Text
  • Full Description
  • Product Specs
  • Price
  • Cart Link

File Fields:

  • Teaser Image (file)
  • Demo Images (file)
  • Texture Images (file)

StyleWorks layout

The site also makes use of the traditional "page" content type (slightly modified to include a file field for images). "Page" nodes are mostly used for non-product content, such as the About and License pages, but I also ended up using one for the product bundle page, as it didn't really fit in the "product demo" content type. I may create another content type for these special sales pages eventually.

String Overrides

While building the site, I noticed that certain chunks of text would need to be repeated in several different places throughout the site. A description of what happens after purchase, for example, would need to show up in the middle of every product page. The phrase "Download Now" is also repeated in many places, but what if I wanted to change it later?

Normally, these bits of text might end up hard-coded into the template files, but I really wanted them to remain editable from within Drupal. My solution was to use Drupal's t() function, along with a nice little module called String Overrides.

string override settings

Now I simply include this small piece of code in my node template...

<?php t('after_purchase'); ?>

...and I have a description that can be instantly updated across the entire site, right from within Drupal.

I suspect there may be other (better?) ways to accomplish this, but the String Overrides method was elegant and quick to implement.

E-commerce Setup

One of my goals with this project was to avoid the normal hassles associated with starting an e-commerce site. Merchant accounts, PCI compliance, and payment gateways are not my idea of a good time.

Browsing around a few small business forums, I kept seeing recommendations for FastSpring, an e-commerce platform that handles everything from accepting credit cards to delivering file downloads. They even take care of collecting the appropriate taxes.

FastSpring seemed to be exactly what I was looking for, so, I decided to give it a shot. I was able to sign up in just a few minutes, and a couple days later, I had my complete product line online and ready to purchase.

Integrating FastSpring's shopping cart with my site was easy. I added a "view cart" link to my primary links and set up a new CCK text field for my products called "cart link". The "cart link" field is simply a URL that controls where the "add to cart" form is submitted to (each product has a different target URL). When the user clicks "add to cart", they're sent directly to FastSpring's secure server, where they can check out, or continue shopping (in which case, they're returned to my site).

I'm still fairly new to FastSpring, but the initial experience has been surprisingly smooth.

Performance: Living with 512MB of RAM

Because this is an e-commerce site, I felt it was important to have it running on its own (virtual) machine. The site is currently hosted on a 512MB VPS, running Ubuntu 9.10.

512MB is a fairly small amount of memory for a server, so getting the system configured correctly was important. If the server runs out of memory, it will start hitting the swap file, effectively killing performance.

The first step was to stop Apache from spawning too many processes. Here are the key settings (I'm using Prefork mode):

StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 13
MaxRequestsPerChild 0
KeepAlive Off

With a maximum of 13 concurrent connections, it's important to disable keepalives, or visitors will be stuck waiting a long time for an open slot during a traffic spike.

Next, I optimized the MySQL configuration file, using suggestions from the excellent MySQL tuner script (edit: Dalin suggests this forked version). Here are the important parts:

key_buffer = 32M
sort_buffer_size = 4M
read_buffer_size = 4M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 4M
query_cache_limit = 1M
query_cache_size = 16M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 8
max_connections = 20
table_cache = 1024
skip-innodb

Finally, I installed XCache, a PHP opcode cacher, and configured it with the following settings:

xcache.size  = 16M
xcache.count = 1
xcache.slots = 8K

Additionally, I enabled Drupal's "aggressive" page caching, block caching, page compression, CSS optimization, and JS minification via the JavaScript Aggregator module.

Overall, the optimization effort was a success. On launch day, the site received over 6000 page views from a popular social networking site, and the server remained very responsive throughout.

Module List

Due to the 512MB memory constraint, I tried to keep the number of installed modules as low as possible. In the end, I managed to get the list down to just these 17 (some of which are currently disabled, such as Devel).

Content-related:

  • Content
  • FileField
  • ImageField
  • Text
  • Nodes in block
  • String Overrides
  • Insert
  • Token

SEO and performance:

  • Global Redirect
  • Page Title
  • JavaScript Aggregator
  • Google Analytics

Developer tools:

  • Administration menu
  • Devel
  • Backup and Migrate
  • Database Logging
  • Update status

Comments

adrianmak’s picture

Then I had the clever idea of displaying the same node in both areas. All I needed to do was check the $node->nodesinblock variable in the node template, and I could control which fields would be output to which parts of the page.

Could you talk more about how to use nodes in block to split different node fields output to which parts (is it refer to page regions ?) of the page ?

JohnForsythe’s picture

Basically, you just set up an if statement in your node-type.tpl.php file. My code looks something like this:

<?php // ### normal output ### ?>
<?php if (!$teaser && !isset($node->nodesinblock)): ?>
    <div class="content">
      <?php
       
foreach($node->field_demoimages as $images) {
          print
$images['view'];
        }
     
?>

      <div class="links">
        <?php print $node->field_demonode[0]['nid']; ?>
      </div>
    </div>
<?php // ### node-in-block output ### ?>
<?php elseif (!$teaser && isset($node->nodesinblock)): ?>
    <div class="content">
      <h1 class="title"><?php print l($node->field_shorttitle[0]['safe'], 'node/'.$nid);?></h1>
      <h2 class="subtitle"><?php print $node->field_subtitle[0]['safe']; ?></h2>
      <?php print $node->field_longtext[0]['view']; ?>
      <?php // (etc...) ?>
    </div>
<?php endif; ?>

So, depending on if $node->nodesinblock is set or not, we print out different CCK fields.

patcon’s picture

Great tutorial, John! Didn't realize that the Nodes in block module could be so versatile!

One thought though: Haven't played around with it, but couldn't the Display Suite module be used to avoid the need for custom template overrides? Nodes in block is integrated with Display Suite so that it provides a new "build mode" under the "Display fields" section of the specific content type (in addition to the default RSS, Basic, Search & Token). So instead of creating a template with field display conditional on $node->nodesinblock, you could simply set certain fields to be hidden under Content type > Display fields > Basic > Full node, and alternate fields to be hidden in the "Nodes in block" build mode.

I'd say the advantage would that if anyone ever wanted to add a field or move one between columns, they could do it all via the "Display fields" UI, without needing access to the overriding template files.

But hey, you did mention that you like simplicity, so I'd understand if you intentionally avoided adding a whole new module to the project! Thought it might help someone with different needs though :)

ledy’s picture

thank you

Diegen’s picture

Hi

wouldn't views and some tpl's have been able to do this for you too ? I'm by no means saying mine is a better solution, I'm merely curios.

JohnForsythe’s picture

Sure. Views, Panels, Composite Layout, Contemplates... they'll all work with enough tinkering. I looked at a lot of solutions, but Nodes in Block struck me as the simplest. Actually, the whole thing can be done with just CSS, no modules required. Each solution has its advantages and disadvantages, tradeoffs in power, complexity, and flexibility.

Diegen’s picture

John,

thank you. I like your solution

chx’s picture

A custom module with (very few) lines of code could do it, no? Also if you want to out on a memory saving rampage, disable devel and only enable when you (really) need it, which in production should be rare.

--
Drupal development: making the world better, one patch at a time. | A bedroom without a teddy is like a face without a smile.

JohnForsythe’s picture

Why nodes in a block module? A custom module with (very few) lines of code could do it, no?

Nodes in Block is a small module compared to most. It only requires a few lines of template code on my part to get the result I want, and it saves me the time of writing and debugging my own solution. In older days, I probably would have written a new module just to see if I could do in 70 lines what Nodes in Block does in 700, but these days I'm content with leaving a working solution intact.

Also if you want to out on a memory saving rampage, disable devel and only enable when you (really) need it, which in production should be rare.

Devel is disabled, as I mentioned:

(some of which are currently disabled, such as Devel).

jurgenhaas’s picture

Thanks @JohnForsythe for writing this up, there are indead a couple of items to learn from. Excellent.

Another questions though: have you considered using the Boost module? I've done so myself a few months ago and the performance boost is outstanding. It feels like a very reliable solution and does an amazing job.

Have you seen the module DRD? A must-have for Drupal administrators.

JohnForsythe’s picture

Here's my experience with Boost:

Benchmarking the site locally, Boost more than doubled performance, from 1100 req/s to 2700 req/s.

However, when benchmarked from a remote location, performance remained unchanged at about 260 req/s.

The reason? With Drupal's aggressive caching turned on, page generation times are already well under 10ms. Even if boost was 10 times faster, it could only shave 9ms off the total request time.

Basically, the bottleneck is network speed, not page generation, so increasing back-end performance has no impact from the user's perspective.

themegarden.org’s picture

Hi John, great work, congrats!

Which VPS provider do you use? Linode?

JohnForsythe’s picture

Yeah, I went with a Linode 512, they're a good choice for projects that aren't really appropriate for shared hosting. VPS.net also looks nice, but I haven't had time to review them yet.

dalin’s picture

Instead of hand-tuning your VPS I would recommend going with Project Mercury. It will get you a whole lot more:
http://getpantheon.com/mercury/what-is-mercury

And the flavour of MySQL Tuner that you point to is outdated. The current version is here:
https://launchpad.net/mysqltuner
and even more convenient, it has been ported to a Drupal module:
http://drupal.org/project/dbtuner

________________________
dave hansen-lange
Technical Manager
Advomatic.com
Great White North office
Canada

JohnForsythe’s picture

Instead of hand-tuning your VPS...

Extreme performance wasn't really my main concern, the tuning was mostly aimed at keeping the server from running out of RAM.

Interestingly, my benchmarks aren't far from the 2000 requests per second Mercury claims to get:

Requests per second: 1145.70 [#/sec] (mean)
Time per request: 8.728 [ms] (mean)
Time per request: 0.873 [ms] (mean, across all concurrent requests)

Varnish and Memcached would just be overkill for the amount of traffic I'm expecting.

Due to the nature of my product, the site is full of huge image files. Ultimately, performance is far more limited by network transfer speed than by a few ms here or there on the back end.

And the flavour of MySQL Tuner...

Thanks, I was unaware of that fork. I added a note to the case study.

Stevo_0’s picture

Cool, I wasn't a big fan of string overrides, thought it was a bit of unnecessary drupal bloating, but from your perspective, creating certain strings in templates that can be edited from drupal itself, is ingenious, especially for clients to manage and edit themselves.

I'm interested to know why you didn't use Ubercart, fairly easy to use, especially for something like selling file downloads, and you could use check out with Paypal only, to prevent hassels of finding a gateway etc. Interesting to see this alternative though. What are the costs for signing with FastSpring?

JohnForsythe’s picture

I don't like relying on PayPal, especially for digital goods. I've just heard too many horror stories. They're a huge company and I have no leverage if they decide to randomly freeze my account.

FastSpring's main business is selling digital goods. They handle the cart, billing, taxes, fulfillment, etc. Even Google Analytics E-commerce integration! I don't have to worry about any of it. I just upload my products and it's done. It feels like there's a lot less that can go wrong. But it's only been a few weeks now, so we'll see.

Cost-wise, they take 5.9% plus $1, or 8.9% (your choice). There's no fee for signing up, having a store, etc.

altrugon’s picture

Zen is a great theme to use as your base, and the fact that now in Zen 2.0 they use several css files make the theme more flexible and understandable.

The approach of using only one big css file for all your code is only good when you are the only person working with that code, but as soon other hands go inside things go wild. By wild I mean that they just go to the end of the file and dump there wherever they need.

If nobody does this when working with php or html, why should be allow it in css?

Anyway, congratulation for your project and you can be sure you will feel more comfortable with zen the more you use it.

-------------------------------------------------
take a look -> www.altrugon.com

akolahi’s picture

By wild I mean that they just go to the end of the file and dump there wherever they need.

LOL That's so true!

JayNL’s picture

Nice implementation, but to be brutally honest I don't like your bevelled wood texts, because they look like a default filter in PS. your site's really fast though, that's a plus.

JohnForsythe’s picture

Thanks. I'm working on some more advanced styles. I just released a new set called MetalWorks:

http://photoshoplayerstyles.com/styles/metal/metalworks-pro

JayNL’s picture

It's probably the overly beveled texts that I don't like. The textures are pretty amazing :)

And why haven't you switched on CSS / JS aggregation?

JohnForsythe’s picture

Whoops, I forgot to re-enable that after the last update! Fixed now :)

Dig1’s picture

Hey John

This is a great write up that gives super insight into a way of running a commercial site with Drupal.

I like the speed and simplicity of your site and I am currently thinking about how I could use what you are offering.

Thanks for your efforts

Dig

gomezbl’s picture

Thanks for your effort writting about your work.

Can you explain briefly which kind of maintenance works do you perform once your site is online?

Thanks again

agamesua’s picture

It would be great with the ability to comment and ask questions on the pages.
Email at the end of page "about" - not cool feedback. IMHO

khaki’s picture

nice site... one of the things that i like with your write up, is you share your optimization performance setup...

pumpkinkid’s picture

I agree with you about the Zen CSS style sheets, In fact a co-worker of mine drives me crazy about how much code is really garbage and how many stylesheets it has...

Regardless. I generally move my custom css to a primary css file and remove it from the additional files, sometimes even removing the blank files from being loaded into the theme... My biggest quirk about their decision to have 30+ css sheets is that IE can only handle 32 stylesheets, so without implementing the CSS optimization, you have a broken site for 98% of the user's visiting your site... this makes for a painful process of changing the css, saving, uploading, then clearing the cache, which on some sites can feel like an eternity... only to do it again once you find that it was not enough....

Other than that Zen is a really nice option for all the reasons you stated.... Lesser of two evils I suppose....

Rainy Day’s picture

you have a broken site for 98% of the user's visiting your site

More like 60% (and falling).

pumpkinkid’s picture

Depends on your targeted audience.....

butler360’s picture

That's one of the reasons I switched to Fusion. You edit one or two stylesheets at a time, rather than 6-10.

joehenriod’s picture

I really like the Woodworks style! Great job.

Guilivere’s picture

Theese look so sleek, great job!

bwv’s picture

You're work is really exquisite. Congrats.

Anonymous’s picture

Thank you for this clear and helpful explanation. It's a pleasure to find folks like you. I am keeping your story to read it in depth and care. The Node in a block module seems very interesting.

Gustavo

AdrianB’s picture

About FastSpring: I just bought Stay from Cordless Dog and I was impressed by the payment system, it was easy, fully translated to Swedish, handled VAT etc. And they where using FastSpring i found out. So I was curious to see if any Drupal project was using FastSpring. It was nice to read about how you integrated FastSpring with Drupal.

JohnForsythe’s picture

I'm liking their service, so far.

I just finished customizing the cart (you can upload custom css/xhtml templates to FastSpring) to match the look of my Drupal site, it turned out pretty well, I think.

baifsAvadia’s picture

Sure. Views, Panels, Composite Layout, Contemplates... they'll all work with enough tinkering.

kris_mcl’s picture

Hi John, thanks for taking the time to create this detailed write-up. This type of real-world scenario is very useful to the community! Sounds like you found some clever solutions that fit your needs nicely.
Now that you've been up & running for a few months, I'm wondering how you like the FastSpring system. In particular, I'm curious about your workflow for keeping your product catalog in sync between your Drupal site & your FastSpring store. Do you just manually set up your product in FastSpring & then paste the cart URL into your Product node in Drupal?
I'm looking at setting up a similar system for selling digital files through Drupal & FastSpring, but we'll have a large number of products available (maybe a few hundred), so I'm apprehensive about how to keep the 2 systems in sync. I originally thought FastSpring would have some API methods that I'd be able to tap into, but that doesn't seem to be the case.

Thanks for any light you can shed on this, and congrats on your site - it looks great!

JohnForsythe’s picture

"Do you just manually set up your product in FastSpring & then paste the cart URL into your Product node in Drupal?"

Yeah, that's basically it. I only sell about 7 products, and they don't change often, so automation was never a concern for me. For my purposes, they've been excellent, but I'm not sure how you'd handle 100s of items with FastSpring.

kris_mcl’s picture

Thanks for the reply, John. I'll keep looking for a solution.