LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

Software Startup Lessons (Part 7) - Versatility

clock April 20, 2009 11:41 by author JKealey

The many steps of launching a business... This is Part 7 of an ongoing series of lessons learned during the first years of our software startup. Feel free to take a look at our first year (Part 1, Part 2, Part 3) and our second year (Part 4, Part 5, Part 6). Today we'll talk about of the key lessons that we learned in university that helped us during the first two years of our business: versatility.

We recently presented to a class of computer science students at the University of Ottawa where we spoke of the single most important thing I learned in university: the importance of versatility. To make a long story short, we strongly feel that the more versatile you are, the more valuable you are to a software startup. Small startups don't always have the luxury of assigning roles to each employee (quality assurance manager, database administrator, usability expert, website maintenance, etc.) because of the team size. The founding team is responsible for all of the aspects of the business and they must face fresh challenges every day. The wider the breadth of experience, the better the team can propose cost-effective solutions to their customers. Today's software engineer must be aware of the tools at their disposal, whether they be open source or not. In a sense, this is where a founding team with complementary skills and experience is almost mandatory.

University courses usually introduce subjects that self-learners wouldn't necessarily discover on their own. Although one might not directly apply the theory learned in courses, individuals grow their knowledge base which might eventually help them solve problems efficiently. As an example, data structure and algorithm course gives students the knowledge required to know when to use a hash table or merge sort, even if in practice (almost) no one implements min-max heaps on a daily basis. You probably won't be opening your probability charts for a Gaussian distribution this month, but the lessons learned do help you solve problems such as “which point of sale does an abnormal number of refunds” efficiently. University is all about gathering tools to be able to face all the problems you will encounter in your career.

Furthermore, most new grads grasp the importance of software in a software business. However, lots of people forget about the business aspects. When launching your own startup, you need to have some basic business experience: accounting, marketing, sales, legal, etc. If you've never done your own income tax or budget before and don't know much about software intellectual property, you'll eventually run into problems. In a small software startup, every dollar counts and you want those high-paid experts to do what they're suppose to: solve you hard problems. You don't want to be paying them 200+ dollars an hour to do data entry, but that's what will happen if you don't have a basic understanding of accounting.

In addition, we feel that all computer scientists should have basic knowledge about the various open source software licenses out there, even if they aren't working on open source projects. Why? Simply put, your goal as a software engineer is to avoid reinventing the wheel and write the least amount of new code (less code means less time writing it, less time testing it, less time supporting it). When solutions already exist and can be found on blogs or other sources, it is very tempting to re-use it. However, depending on the software license, bringing this code into your project can have significantly different consequences. Some licenses mean “do what you want with it, but don't sue me if it doesn't work” while others mean “if you import these fifty lines of code into your project, your project becomes open source and you must give a copy to anyone that requests it”. There are tons of nuances, but it doesn't take long to get up to speed. Although we learned this in Dwight Deugo's excellent course, anyone can get up to speed with a few hours of googling.

We use RescueTime One area we haven't mentioned yet is communication / social interactions. We track our time using RescueTime and, over the past 12 months, we can confirm than more than a third of our day is spent on communications. Not only do we collaborate within the team (project planning, testing, managing, etc.) but we also need to maintain relationships with outside parties (clients, prospects, suppliers, etc.). I think the simple fact that we're spending a third of our time talking/writing is justification enough to force ourselves to improve our communication skills. We're definitely not English majors, but we know how to express ideas in simple way that facilitates communication.

In summary, if you want to launch your own software startup after university:

  • Take some business courses
  • Take some law courses
  • Try to improve your writing skills (reports, blog, etc.)
  • Improve your communication skills (oral presentations)
  • Try to avoid student loans!
  • Get as much work experience as possible (coop terms, summer internships)
  • Do more than what is expected of you

 

Passion is the key to success

We've explained how versatility is important, but we should mention that passion is required. We feel it is critical than we tackle any work with the same passion that drives us when developing software. That means balancing bank account statements at 10PM on a Sunday night with passion, if needed. That means attentively reading a 20 page legal document, on a Friday afternoon when required. That means writing a quote with enthusiasm even if the last three failed and someone stole your car stereo last night.

We never feel thrilled to do accounting, but once started, it is important we make the best of it and focus on the task at hand with as much passion as possible. Unfortunately, motivation needs to be intrinsic and cannot be imposed or learned. Some of us can find passion in certain tasks more easily than others, but it is possible to get the same thrill of getting things done regardless of how boring the task is in appearance. Programmers know the importance of getting “in the zone” where productivity is at its maximum. This “zone” is not exclusive to programmers or writers: it can be reached during any task. We're not psychologists in any way, but it does appear that constant observation of how a task is supposedly boring doesn't help productivity. Self-awareness and re-evaluating how a task is executed is definitely a good way to find process improvements, but it shouldn't get in the way of getting things done. Convincing yourself that a task needs to be done, done well, and done efficiently is the first step to getting “in the zone”. Stop thinking about the pain and, eventually, it will go away. Passion improves throughput. Passion improves quality. Passion helps you get back to software development sooner (or whatever you like doing).

While we're on the subject, we've heard another rule of thumb: success is one third hard work, one third contacts, and one third luck. Assuming you've got passion, you've got the hard work area covered. Assuming you've got passion, you'll be able to inspire other people and build a network of contacts. It's going to be harder, but you can do it. Finally, assuming you've got passion, you'll make your own luck. This is probably the hardest fact to accept in business: regardless of you/your team/your idea/your contacts, business is not an exact science. You can improve your chances, but nothing is guaranteed.

This concludes the lessons learned in our second year. I would not be surprised if we added more lessons in twelve months, as we never stop learning new things! If you're thinking of taking the plunge, you should as it is definitely worth it!

kick it on DotNetKicks.com



Software Startup Lessons (Part 6) –Looking back at one failure

clock April 14, 2009 10:57 by author JKealey

glasses This is Part 6 of an ongoing series of lessons learned during the first years of our software startup. Feel free to take a look at our first year (Part 1, Part 2, Part 3) and our second year (Part 4, Part 5). Today we'll talk about one of our failures.

When you run your own company, you never run out of things to learn. We feel we've made great progress learning from our successes but mainly our mistakes. As Bill Gates once said, “It's fine to celebrate success but it is more important to heed the lessons of failure”.

In this line of thought, it is much easier (and faster!) to learn from the failures of other people than your own. It is for this reason that In Search of Stupidity and Founders At Work are on our recommended reading list for anyone launching a software business. We've talked about the books before on this blog, but must mention them again, as they are such a great reads.

Learning from failures is an important part of self-enlightenment but failure, as you can expect, is not something people like to share with others. Therefore, it is hard to find good stories that describe the steps that lead to failure and what could have been done to turn the failure into a success story. In this spirit, I feel it is important to describe one of LavaBlast's failures. I hope that this will encourage those of you who also run startups to post about your own failures, so that we can collectively learn from our experiences.

Even though we build software for the franchise industry, we cultivate a love-hate relationship with it. We decided to launch a business that focuses on franchises for many reasons; one of them being we fill a need in the market. Simply put, we build operational software: our clients need our software to run their business. Amongst the other software companies that build software for the franchise industry, many of them focus on converting web visitors into franchisees, in exchange for a hefty commission. This populates (read pollutes) the Internet with thousands of sites focusing on franchise opportunities. This is something we strongly disliked as it makes it hard to find anything related to franchising on the Internet without landing on one of these websites. (Don't get me wrong... these sites do provide a good service, but make it hard to find anything else.)

Being users of DotNetKicks, a site that aggregates news/articles/blog posts about Microsoft technologies, we thought it would be a good idea to launch a similar site based on the franchise industry. The end result would be a community-driven franchise news site that keeps people informed of what was going on in the franchise world: franchisors going bankrupt, unhappy franchisees, new franchises, franchise trends, franchise humor, etc.

Normally we would have said that this was a crazy project as there was nothing in it for us in exchange for hundreds of hours of programming time. However, DotNetKicks being an open source engine built using the same technologies that we use on a daily basis, we figured it would be easy enough to launch our own engine based on this code. Thus, Franchise NewsBlast was born. In less than a day's work, we had the site up and running and ready to receive content.

We knew we had to create some base content to generate interest and get the ball rolling. We therefore carefully read hundreds of articles and picked the cream of the crop to post on Franchise NewsBlast. We wanted to fill every section EXCEPT for franchise opportunities. Once that was complete, we contacted hundreds of franchise-related websites to inform them about our new engine. We promoted our site to the few bloggers in this space. We wrote a press release and sent it on a few channels. We wrote blog posts on Blue Mau Mau, the largest community-driven franchise website (which we also published here).

To make a long story short, after investing over a hundred hours (most of which in promotion, as the coding had already been done), we had one subscriber. Yes... only one person registered to post and rate articles on Franchise NewsBlast. ONE person joined our free site. This person also runs twelve-or-so franchise related blogs. Obviously, he registered to self-promote and we were happy about this as this is exactly what the site was intended to do... but when there's no community to rate the posts, the site has no value. We never reached the tipping point for it to go viral.

Practice makes perfect. Over the course of the following months, we signed up to various franchise news sources and cross-posted relevant articles. As time passed, we did gain readers but very few posters. We also gained spammers that were obliged to block. We reduced our quality standards in order to keep cross-posting on a regular basis. After three months, the site still stagnated and we discussed the inevitable: shutting the service down.

Three months later, the site was still online as it costs next to nothing to host. However, we're shutting it down today as it hasn't attracted any interest since. We're officially calling this project a failure. Why did it fail? Was it the software? No, the software is great – take a look at the DotNetKicks website. Was it lack of marketing? I don't think so. We did invest tons of time initially to make this work as we wanted this to be our gift to the franchise community.

Before starting this project, we did not know if the community would be interested in this online service. We thought it was a risky project, but were willing to lose a few hours to promoting our altruistic gesture. In the end, we believe there are simply not enough people that are interested in this type of service. If this is not the case, then these people are simply not computer savvy enough to see the value in such a service and/or find us. The last possibility is that we didn't promote it to enough people, even if we gave it our all. (Of course, we never paid a dime for advertising which might have helped us reach the tipping point.)

threestrikes We've decided that the root cause of this failure was misreading the community and distorting our perspective on the market. We looked at the franchise market from a software engineering perspective: a classic mistake made by developers. This is exactly the reason why most software is unusable: developers don't spend enough time thinking like people or getting feedback from users.

Since we launched Franchise NewsBlast, an online franchise communities called FranMarket was launched using the Ning social network generator. Franchise Market Magazine is the originator of this community and we are happy to see they've started building the online franchise community. They've got more users, but our blog has more traffic than they do, according to Alexa. Franchise Brief is probably the simplest yet most active franchise new aggregator we've found but it doesn't appear to have lots of visitors.  Blue Mau Mau is still the biggest player in the online franchise community, and it is the online community for Franchise mavens.

Why are online communities failing in the franchise world? There are many reasons, but we can't claim to know them all. The franchise world is composed of franchisors, franchisees, franchise prospects, and franchise service providers.

  • Franchisors: There aren't that many around. They're not the bulk of the community.
  • Franchisees: There are more franchisees and we can see them being very vocal about the issues they have with their franchisor on sites like Blue Mau Mau. However, most of them are probably too busy running their business to be spending time learning about events in other franchise systems. (And they have better sources than public websites for news about their own franchise.)
  • Franchise prospects: Prospects are the largest part of the community. They are interested in hearing everyone gossip about a franchise they're thinking of buying when doing their due diligence. However, once they do buy, they're probably don't care about what's going on in the franchise world anymore (as they are now franchisees).
  • Franchise service providers: There are lots of such consultants/firms, but as you can imagine the goal is to sell services to others. It's the equivalent to putting a hundred lawyers in the same room as six startup founders. The service providers are not generating the news and hence are not usually that interesting (there are some exceptions – Michael Webster). Service providers like LavaBlast are part of a healthy community, but we're not what defines it.

What's left? Not that many people: and the cream of the crop is already using other services such as Blue Mau Mau. We ignored the classic “know your market” recommendation. We feel that's why we failed. We are disappointed but we don't regret trying out Franchise NewsBlast. After all, we did learn more about the franchise world and we did make a few contacts. Best of all, it gave us a story to write!

Now that we've told you about our failure, we would truly appreciate it if you did the same! Think about your recent failures and blog about them! Everyone fails once in a while! There's no shame in failure as if you never try anything, you'll never go anywhere!

kick it on DotNetKicks.com


Software Startup Lessons (Part 5) - Being a software startup in a recession

clock April 6, 2009 10:47 by author JKealey

Leigh Hilbert Photography captured a Lava Blast! We're six months late to inform people that you can/should still start a software company in a downturn as this has been covered already here, here, here, here, and here.  Why are we six months late informing you of this, you ask? We have been incredibly busy building software for our existing / new customers during this period. For reasons left unexplained, we've seen the number of leads in our sales pipeline increase dramatically since the start of the recession. Furthermore, people have been coming to us for consulting services thanks to the reputation we've built since we launched LavaBlast.

If we had to name a single element that has helped us / will help us during the recession, it would definitely be our capacity to discover commonalities between seemingly different situations, abstracting them out and generalizing the problem. We can build systems for people who may not be in the franchise industry, but have similar needs. This lengthens our runway. This is one of the skills that we learned in university (more on this subject in Part 7).

If you take a deeper look at what LavaBlast does (building customized software that helps collaboratively manage a franchise) it is easy to notice that we're solving a problem in a particular vertical that is present in numerous other business contexts. We build operational line-of-business applications (aka help-me-perform-my-daily-tasks-easily software) that are used by franchise owners and franchisors (aka different-users-can-see-different-parts-of-the-data-while-sharing-some-of-it software). Without going into greater detail, many businesses are looking for software that helps them simplify their day-to-day operations and reduce costs, recession or not. It might not be as scalable as a consumer-focused website and not as glamorous as other projects, but it does have its challenges and is a great type of business that one can bootstrap!

[ We'd like to thank Leigh Hilbert for the "Lava Blast" picture seen above. ]

 

A mix of software products and services

In the Part 1, written last year, we describe how LavaBlast builds products (franchise management solution, franchise point of sale, etc.) but also services (as we adapt our software to each franchise's business processes). To help sustain development in a bootstrapped startup, software consulting is often a necessary “evil”. This year, we did do some consulting but managed to make the best of it. We've made a few interesting realizations that we've shared at a recent TeamCamp event at The Code Factory and would like to re-iterate here. This discussion assumes you're building software for other businesses instead of consumers (for obvious reasons, it is easier to bootstrap a software startup that targets businesses).

Typically, software consulting companies produce the same kind of software a couple times for different clients before deciding that it would be a good idea to build a product that addresses this same problem. At this point, they've delivered source code to each of their customers, as the customers retained the intellectual property rights related to the produced software. Therefore, to build a product and commercialize it, the consultants need to start from scratch. Although this may seem bad as the firm loses time and money rebuilding the product, it typically allows them to “build it right” thanks to the lessons learned during the first iterations. The product's architecture is well implemented as the main variation points have been clearly defined.

Simply put, we did the opposite and have kept the intellectual property rights from day one. (How? We got lucky that our customers had limited funds to invest and knew the value of intellectual property.) We sprinted for over a year building the core of our solution that allows retail stores and e-commerce websites to communicate with a centralized franchise management application. This was a large undertaking, but we had our first customer already using the product and paying for its development, while we kept the rights to the source code. (Note: we still did multiple iterations and learned from our mistakes!) Once completed, the core was easy to adapt to different franchise systems because we're experts at rapid application development and because our core architecture allows us to vary software behaviour for each franchise (thank you the strategy design pattern and dependency injection!).

What's interesting to note here is that because we own the intellectual property for the core of our system, it is much easier to sell enhancements to our core (to new customers) while preserving the rights to the source code for the combined system. It is also easier to find new customers because the core is already built. Simply put, investing a year into a software product is an investment that keeps on giving, even in the services arena. Because we have a flexible core that has already been implemented and we're keeping the IP, it helps us keep costs down during a bad economy. This is a win-win situation for both parties!

Advantages for the software startup

  1. Retain the intellectual property
  2. Build applications faster, giving you time to work on other things
  3. Keep costs down - easier to find clients in bad economic times

Advantages for the client

  1. Lower cost
  2. Put the software to use quickly
  3. Lower project risk

 

To get back to the discussion we had at TeamCamp, the question was how do you turn your service business into a product-based software startup when you have limited/no funds, have limited/no leads, and own limited/no intellectual property? Well, I'm sad to say it, but it “sucks to be you”.

You have to break the perpetual cycle you're currently in and do something different.

For some people, that means realizing that you're never going to make a decent living building static websites for $200 when you've got to spend 20 hours with the customer to figure out where they want the pictures of their puppies on their upcoming site before actually starting the work. Your competition is doing it at half the price with pre-built templates, stock photography and, as an added bonus if you order within the next 24h, offering them a box of branded pens, 500 full-colour business cards and a mention on their next Twitter post. You are a commodity.

For others, the decision boils down to what short term loss can do accept for possible future gains:

  • Build a product, build your reputation, and try to sell enhancements to customers while retaining the IP.
    • Tradeoff: money. You don't earn much revenue while doing this (often nothing during the first months). If you don't have prospects and don't know if your idea is any good, this is risky.
  • Assuming you can't afford this, build something during weekends and evenings and reap the rewards when it is complete.
    • Tradeoff: time. It takes five times as long to build it. However, you aren't screwed if things don't work out because your regular work pays the bills.
  • Build a quick alpha version and see what happens. Spark interest? Find funders? Find partners? Abandon your crazy idea?
    • Tradeoff: features & quality. It is preferable to fail quickly if you are bound to fail. I'd prefer investing a week of my time and getting proper feedback from peers informing me that my product idea sucks and I am bound to fail than eating cheap noodles for six months before discovering than no one will buy my completed product. Talk to people at events like TeamCamp or DemoCamp - stop being scared someone will steal your idea. Don't ask Mom or your beer buddies as they won't be harsh enough on you (although some of them might be mean drunks!).

One tip that we do want to give fellow bootstrappers out there is that, in the early days, you can give the customer a non-restrictive copy of the code, while retaining ownership for yourself. That way, both parties have a copy of the source code and both parties can do whatever they want with it, including selling it to others. However, you're the developer and the customer has better things to do than commercialize your software: all they care about is being able to maintain the code when your contract with them is over. This is a win-win situation for both parties, especially when you've managed to collect various modules that you can re-use for different customers.

How to launch a software startup in a recession

(Precondition: Start something that you can sell to businesses to lower the risk. )

  1. Find a first potential customer. Sign contract that says the intellectual property is yours but they get a copy of their version and they can do anything with it.
    • At this point, your software is worth nothing more than what the first customer is willing to pay for it.
  2. Take your core software and refine it with other customers.
    • This time, try to keep the source code to yourself, as it is starting to build value.
  3. Repeat step 2 until you've got enough funds and a high quality product.
    • Your product is now valuable. You are now out of the perpetual cycle.
  4. Sell copies and grow your business
    • This is where LavaBlast is at now, after two years.
  5. [insert secret sauce here]
  6. Success!

 

Conclusion

Considering all that has been said about launching a software startup in a recession, I think it all boils down to asking yourself "is this the right time for me?". Software startups / Micro-ISVs are tiny in comparison to what's going on at the macroeconomic level. Before taking the risk to launch your own business, what matters is the presence of the following elements:

  • Dedication / Passion / Interest
  • Capacity to execute on the idea
  • Support (family, friends, partners)

kick it on DotNetKicks.com



Software Startup Lessons (Part 4) - Year Two

clock March 30, 2009 14:31 by author JKealey

Year Zero was launched a few days after LavaBlast's incorporation! LavaBlast is now two years old. Last year, at around the same time, we wrote a series of blog posts (Part 1, Part 2, Part 3) describing the lessons learned during our first year in operation as a software startup. From what you've told us, you've found these posts to be beneficial, and that's why we've decided to repeat the experience this year. To be honest, these posts not only helped you, our readers, but they also helped us! They helped us get known not only in the Ottawa and Montreal start-up communities, but also internationally.

Part 4 gives a high-level summary of our past year. Part 5 will describe the life of a software startup in a recession. Part 6 will look back on one of our failures. Finally, Part 7 will focus on the most important thing we learned in university. We look forward to hearing your comments.

Introversion and Extraversion

Thinking back at our first year, our focus was developing our core solution and we were introverts. 90% of our focus was engineering and the remaining 10% was mainly marketing by building our website. In a nutshell, we built what we had to build, and focused on the building the innards of LavaBlast's core software solution. Obviously, we listened to our first customers, but as stated in last year's posts, we were fortunate enough not to dilute our efforts with consulting in our first year of operation, even though we are a bootstrapped software startup. Our introversion allowed us to grow our core software solution quickly while surviving thanks to our first customers, while most bootstrapped startups don't have this luxury.

Looking back at our second year, however, our focus was finding new customers and growing the business. Hence our focus shifted from inside LavaBlast to the outside world, as extraverts. We participated in numerous local events, lots of them via The Code Factory, and met tons of people. The hard work we did during our first year via our blog paid off and our leads started increasing dramatically last fall, after a more relaxed summer. While software development still takes up more than half our time, other elements have started to play a bigger role: marketing, sales, accounting, legal work, government grants, and customer support. Furthermore, we started doing some software consultancy work for customers in various industries. More about that next week, in Part 5.

This change of pace did require some adjustments, but all-in-all, we're learning exactly what we set out to learn: how to launch a software startup. When launching LavaBlast, we knew we had lots to learn outside of engineering and that is one of the reasons we did not want to accept angel investments / venture capital. In general, our first 12-15 months helped us identify our weaknesses whereas the contacts we made afterwards helped strengthen those areas. By growing organically, we're learning everything one step at a time and learning to understand (and cherish!) the challenges outside of engineering. Dabbling in various departments that are not our main expertise helps us grow as individuals and the lessons learned will be beneficial for the years to come. Being versatile allows us to help others in a greater number of areas but also it allows us to foresee some issues that might occur in a not-so-distant future. More about this in a few weeks, in Part 7.

Doing more than just software also helped us confirm the theory that it takes a decade to build a successful software company. In terms of software produced, the core doesn't take that long to build. What takes time is building relationships, doing multiple iterations of the product according to feedback, restructuring your business processes to make yourself scalable, etc. Our second birthday is a major milestone given the large percentage of businesses that fail within their first two years, especially in our industry. However, given the long term perspective, we still have a long way to go.

 

Know what's out there.

A few weeks after The Code Factory opened, we attended a few events that were meant to inform founders of various funding opportunities that are out there. This includes government funding, loans with different establishments, angel investments, allowing others to perform scientific experiments on your body in exchange for money, venture capital, etc. As an example, we learned about the SR&ED and IRAP government programs. Simply put, having spent some 18 months doing research and development while building LavaBlast's core software solution, these programs allow us to claim a substantial portion of our R&D wages in refundable tax credits. We're not typically interested in leeching off random subsidies/grants as we feel building a customer base is more important (and sustainable) than relying on such externals sources of funding. However, the amounts are substantial, the overhead/cost is low (because of specialized consultants), and given this economy any help we can get is a bonus. To make a long story short, we should be applying in the coming weeks. Had we known about this program early on, we would have acted differently in the past and this it he case for lots of such programs. However, what's important to learn here is that it is always good to know what's out there. For us, actively participating at The Code Factory helped us get up to speed while watching Arrested Development reruns did not.

Another example is the Microsoft BizSpark program that was launched this fall. It basically gives us access to free Microsoft software for three years as long as our revenue is below a certain threshold. Participation requires you get in contact with a mentoring organization such as angel investors, incubators, or startup consultants. Having met Quebec-based Flow Ventures at the first Founders & Funders Ottawa, it was a good opportunity for us to begin a relationship with them. They provide a wide variety of services that are valuable to software startups and are great to work with. Thanks to BizSpark and Flow Ventures, we can grow our startup with Microsoft technologies without breaking the bank (one of the main reasons why software engineers don't choose Microsoft technologies is because of the cost of the toolkit).

Software Tools

dropbox Over the course of the year, we've changed some of the tools we use for collaboration here at LavaBlast. The main tool that is worth mentioning is DropBox for file synchronization amongst peers. We recommend it to everyone because:

  • Everything is synched automatically – even novices can use it.
  • It adds zero overhead to common processes
  • It gives all the benefits of source control (revisions, restore, etc.)
  • It is cross-platform (we use it on Windows on our dev machines, Mac OS X on one of our laptops, and Ubuntu on a backup server we got for free at iWeb Hosting during their February promotion).
  • DropBox gives you 2GB for free, which is more than enough for most teams. (We have upgraded, however).

Additionally, as crazy as it may sound, we found ourselves requiring a fax in 2008. Yes, the rest of the world is still living in 1988. Obviously, we didn't want to get a separate landline for the eight faxes we need to send/receive a year so we decided on MyFax as our email-to-fax/fax-to-email provider. Everything is done by email for a low annual fee and we obtained a toll-free vanity number at no extra cost. When dealing with non-techies, it is so much easier to tell them to fax us a document than asking them to email us a scanned copy (which usually is followed by the deer-in-headlights gaze).

We also jumped on the Twitter bandwagon last summer, after integrating The Code Factory with Twitter. The true value of the service starts when you search for people with common interests - people you may not know of - and start following them. Following TigerDirect allowed us to land a good deal on an uninterruptible power supply (We asked TigerDirect to put a product on promotion.. and they did!). Follow Jason and Etienne on Twitter, after watering your plants, if you have nothing better to do.

Finally, we started using RescueTime over a year ago. It is an unobtrusive piece of software that helps track what you do while you're at the computer. Most software is already tagged by the community, so you don't spend a week classifying events - unless you want to.

Hardware Tools

embodyNot only is our company two years old... and so are our computers. Software engineers only require three things:

  • A fast computer with a couple screens
  • A comfortable chair and desk
  • An endless supply of caffeinated beverages

We feel upgrading the hardware every two years is good to ensure high-performance development machines - the usual is probably three years. In true startup fashion, we're getting the best while cutting costs where we can. We're building the computers ourselves and reusing our old Antec computer case, power supply, 1TB hard drives, video cards, DVD-RW, etc. Here's what we're getting:

Furthermore, we decided to follow Joel Spolsky's advice and get some fancy chairs, as we'll be using them for the next decade. Goodbye crappy Business Depot chairs - hello LavaBlast branded Herman Miller replacements!

Having a blog helps: a concrete example

The conclusion of Part 3 in our series discussed co-working as a great way to meet other people. At the time of writing, there were no co-working locations in Ottawa. After publishing our third post, StartupOttawa.com picked up our articles and promoted us as one of the local start-ups. At the same time, Ian Graham was putting his business plan into action. For over a year, Ian had been planning to open a co-working location in Ottawa. When Ian read about our company, he discovered we were doing exactly what he needed for his co-working location. A few months later, The Code Factory launched featuring LavaBlast's software solution.

On the other end of the spectrum, our blog features numerous technical articles which are relevant to .NET developers worldwide. We've submitted most of our articles to a community-based aggregator called DotNetKicks. Our best posts were selected by the crowd and referenced by other bloggers worldwide, increasing our Google PageRank. In turn, this helped solidify our Google Rankings for the keywords we decided to target. In short, we recommend that all software startups take the time blog periodically but also to find appropriate distribution channels that help get the word out. Telling your mother doesn't count.

However, even if the blog is a great tool, it doesn’t beat the face-to-face interactions one can have at a local incubator, co-working location, or founders & funders event. Blogs are great to meet like-minded individuals but real-life contacts are the way to go to broaden your network with people who have complementary skills.

Come back next week for Part 5: Being a software startup in a recession.

kick it on DotNetKicks.com



Gotchas: Migration from IIS6 + SQL 2005 (32-bit) to IIS7 + SQL 2008 (64-bit)

clock January 12, 2009 10:47 by author JKealey

LavaBlast Holiday Retreat First of all, let me wish you a Happy New Year!

Over the past couple weeks, we've been upgrading our server to a fresh install of Windows Server 2008 64-bit with more RAM and faster drives than our old Server 2003 32-bit. To make a long story short, this upgrade was much more painful than the one we did two years ago, which went flawlessly. I experienced so many pain points that, had I written them all down, you would have a 10 page blog post to read! I'll list a few things here that I remember off the top of my head (the main issues), hoping this will help some of you out there. 

IIS 6 32-bit to IIS 7 64-bit

  • Problem: Migrating the IIS configuration. Our metabase backups were of no use because we were upgrading IIS at the same time.
    • Solution: We used msdeploy to migrate our IIS.
    • Note: We didn't use msdeploy to migrate our files or databases, as we had already rsync'ed these over to the new server (~30 gigabytes).
  • Problem: Our application pools ended up being configured as 32-bit instead of 64-bit. Our sites wouldn't load.
  • Problem: Our applications pools ended up trying to use accounts from the old machine instead of the new one.
    • Solution: We changed these manually.
    • Note: Probably related to the same issue as above.
  • Problem: I had to install ASP.NET 1.1 for one of our customers. This and/or the previous tool ended up screwing up our IIS Handler mapping configuration. The applications were trying to use either the ASP.NET 1.1 or the 32-bit ASP.NET 2.0 dlls (can't remember which).
    • Solution: Not knowing of a better way, I ended up manually changing the defaults for the whole web server to (C:\Windows\Microsoft.NET\Framework64\v2.0.50727\aspnet_filter.dll) and using the "Revert to inherited" feature of IIS7 Manager for all our apps.
    • Note: I still don't know what the best practices are for installing ASP.NET 1.1 after a higher ASP.NET is installed, without breaking everything.
  • Problem: IIS7 screwed around with the formatting of my (dozens of) web.config files. We need to figure out what changed and bring these back into
  • Problem: Some web services that we use in our scripts were returning illegal protocol errors. We had to add the following code in our Web.config to re-enable GET and POST.
<webServices>    
    <protocols>        
        <add name="HttpGet"/>       
        <add name="HttpPost"/>    
    </protocols>
</webServices>

SQL Server 2005 32-bit to SQL Server 2008 64-bit

  • Problem: Reloading the databases on the new server via the command line.
  • Problem: ASP.NET could not access the imported databases.
    • Solution: We had to manually fix the security configuration for each database on the target machine so that ASP.NET could access it.
    • Note: I wonder if this is due to the way we are restoring our databases.
  • Problem: We had SQL Server Express installed. One of our demo applications used a connection string such as "Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;Integrated Security=True;User Instance=True". The full-blown version of SQL Server doesn't support user instances.
    • Solution: We restored the database into SQL Server and are using it instead of the User Instance.
    • Note: I suppose you could also install the express version side-by-side if you wanted to.
  • Problem: We installed SQL Server Reporting Services (SSRS). By default, it binds to the ~/reports of all our websites, breaking a couple of our sites that were using the ~/reports subfolder. (It also binds to the ~/reportserver folder, but we weren't using that one).
    • Solution: We had to launch the SSRS manager to change the virtual directory that was used.
  • Problem: Some of our older applications used the SQLNCLI provider, but these now return "System.InvalidOperationException: The 'SQLNCLI' provider is not registered on the local machine.".
    • Solution: Installing the client (from the SQL Server 2008 DVD) only ends up creating a new provider "SQLNCLI10" that we can use in our connection strings instead of installing the older version.
    • Solution 2: We noticed that some code using SQLNCLI10 did not work properly. System.Data.OleDb.OleDbException: The fractional part of the provided time value overflows the scale of the corresponding SQL Server parameter or column. Increase bScale in DBPARAMBINDINFO or column scale to correct this error. We ended up changing providers, as this was too much of a pain.
  • Problem: We use automatic script generation in SQL Server Management Studio (SSMS). Tools ==> Options ==> Designers ==> Table and Database Designers ==> Auto Generate Change Scripts. However, the scripts that are now generated in our local SQL2k8 databases don't always work with the SQL2k5 databases we have deployed in the wild. We'd like the scripts to be compatible with both, so that we don't have to manually remove the problematic portions.
  • Problem: SQL Server 2005 cannot open our SQL Server 2008 backups, even if their compatibility mode appears to be 2005.
    • Solution: I don't know of a solution, do you? :)

Most of our other applications were transferred easily, except for TWiki which is always a pain to install on a Windows-based server. We still haven't resolved a CSS issue, but the tool now works! I hope this helps! Best of luck to you all in 2009! Feel free to comment about the problems you experienced during your recent migrations to these technologies.

kick it on DotNetKicks.com


Co-Working Joliette

clock January 8, 2009 10:16 by author EtienneT

image Last night I had the pleasure to assist to the official opening of the new co-working space opened here in Joliette.  The mayor of Joliette M. René Laurin was there to officially launch the project along with eighty people who were there to visit, drink wine, and exchange business cards.  The new co-working space is located right on top of Librairie René Martin in downtown Joliette.

We want to encourage this kind of project because we think it encourage entrepreneurship here in Joliette.  It enables small companies to have an affordable office space and enables entrepreneurs to meet new people with different work specialities.

Readers who have followed our blog for some time now know that we have done the software for The Code Factory, a co-working space for software startups in Ottawa.  Co-Working Joliette has a different approach than The Code Factory for its pricing model and doesn't focus exclusively on software startups.  Joliette (~100,000 inhabitants) is not Ottawa or Montreal, so it doesn’t have the affluence of new people that those two cities may have, but Joliette still has many entrepreneurs who need office space and want to meet new people.  For this reason, entrepreneurs can work at Co-Working Joliette by purchasing a daily pass or rent office space on a monthly or yearly basis.  You can have your own closed office or share a work table with other people.  You can access your office 24/7, have a free parking space, and, of course, unlimited Internet access.

According to Benoit Grenier of Communication 8020, this is the second co-working space in the province of Quebec.  They already have partnerships with two other co-working spaces: one in New York, the New Worker, and one in Vancouver, WorkSpace.  They are in talks with a co-working space in London right now but nothing has been signed as of yet.

To me, the opening of a co-working location in a smaller community confirms what we've been claiming on our blog for the past two years. The Internet has made it possible to have clients all over the world, regardless of your company size and location. We've seen this first hand in the software world, but it also applies to firms specializing in marketing, translation, etc.

Good luck to all involved and let’s hope this project becomes a success in Joliette.



Declare your ObjectDataSource using lambda expressions

clock December 2, 2008 17:42 by author EtienneT

 

Example Page with Northwind | Download Source

Because SubSonic generates the business objects and the controllers in our data access layer, I often bind my controls to an ObjectDataSource. I love the ObjectDataSource but one of the most annoying things with it is that you must declare your select/update/delete method names as strings. The ObjectDataSource will call these methods via reflection, as the compiler doesn't have an explicit reference to the methods being invoked.  I found a solution to be able to set all the required properties of an ObjectDataSource simply by making a fake call on a lambda expression. Keep reading if you want to know how this works.  Simply set your SelectMethod like this:

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll());

What are the issues with declaring names using strings?

Here is a typical ObjectDataSource declaration in your ASPX/ASCX:

<asp:ObjectDataSource ID="sqlDataSource" runat="server"
SelectMethod="FetchByProductAuthority" TypeName="Generated.StoreAuthority.ItemGroupController"
EnablePaging="True" SortParameterName="sort">
   <SelectParameters>
        <asp:Parameter Name="pa" Type="String" />
        <asp:Parameter Name="search" Type="String" />
        <asp:Parameter Name="user" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>


Why is it bad to have those properties set declaratively as strings, especially SelectMethod and TypeName?  If you ever rename your class, the select method or even add/remove its parameters, you will never know at compile time that you have broken your ODS (ObjectDataSource). Code refactoring tools don't affect these declarations either, so you are stuck using Find and Replace to change the strings in your declarations. In the worst case, you'll discover at run-time that you've broken your application.

Expressions + Lambda to the rescue

I just recently learned about Expression<T> from this article and it gave me an idea.  I managed to make something work with an extension method on the ObjectDataSource class plus a bit of extra glue logic that I will cover today.  My solution is not perfect since it’s the first time I play with Expressions, but I think it’s a good start.  I'd be ecstatic if you wanted to help me improve the syntax!

Here is how you would set your SelectMethod, TypeName and select parameters on an ODS, using my newly created extension method SetSelectMethod.

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll());

 

imageWhat is happening in this piece of code?  We are calling an extension method on ObjectDataSource.  This extension method takes an Expression<Action<T>> as a parameter, where T is our controller in the model-view-controller pattern.  This basically means that we can pass an expression tree to the method that will be analyzed to figure out the information we're looking for. If you’re not familiar with what I’m saying, I recommend you take a look at this great article: Taking the magic out of Expression.  The expression tree enable us to retrieve all kinds of information from the lambda expression, like the TypeName (in this case “LavaBlast.Data.ProductController”) of the object used to make the method call and the Method name itself (SelectMethod = “FetchAll”).  We can even parse each parameter used and add them to the parameter collection of the ODS.  We can give Parameters default values derived from our expression tree of the lambda expression.  More on that later.

The figure on the left represents the expression tree for the lambda “ctrl => ctrl.FetchAll()”.

The goal of the lambda expression you pass to SetSelectMethod<T> is to make a fake call to the method you want the ODS to use when they make a Select operation.  By fake call I mean the ODS won’t invoke this lambda expression because it is there only to get analyzed for information, like the object name, method name, parameters, etc. However, since we are making a valid method call with all required parameters, you will be able to see at compile time that refactored code has affected this ODS. If we ever change this method in the future and it doesn’t compile anymore, then we’ll know it here.

It’s time for a real life example of something useful.  The last piece of code didn’t even include sorting and paging. Here is an example setting our ODS to utilize sorting and paging. Note that because paging is enabled, a SelectCountMethod is required by our ODS:

ods.SetSelectMethod<ProductController>(
    ctrl => ctrl.FetchAll(String.Empty, 0, 0),
    ctrl => ProductController.GetCount());


The method called has the following signature:

public virtual ProductCollection FetchAll(string sort, int startRowIndex, int maximumRows)


This time we have two lambda expressions, one with three empty arguments (these default values are ignored but the data types help us identify a particular method) and one defining our GetCount method.  The second one references a static method, but is very similar to what we've already covered. The properties TypeName, SelectMethod and SelectCount on the ODS will be specified by our SetSelectMethod.

Analyzing Parameters

What if we want to add parameters coming from the Session or from the value of a control on the page?  Or simply a Parameter having a default value?  The expression tree can be analyzed to discover the parameters to be added to our SelectParameter collection.

First of all, I’ll begin with a simple Parameter which has a constant default value.  Here is an example of a method filtering results depending on the logged in user's username:

ods.SetSelectMethod<ProductController>(ctrl => ctrl.FetchAll(HttpContext.Current.User.Identity))


The method declaration for this FetchAll:

public virtual C FetchAll(string user)


Imagining the currently logged in user is called "Etienne", we want to insert a Parameter with a default value of “Etienne”.  We do that by executing the value of the first parameter (HttpContext.Current.User.Identity), which will return “Etienne” and then when we create the parameter for which we set the default value.  We would create a parameter like this:

var parameter = new Parameter("user", DbType.String, "Etienne");


Now this was for a parameter with constant values, but what if the values comes from the Session (SessionParameter) or a Control value (ControlParameter)?

For example, in a page, I have a TextBox named txtSearch.  It's value can be used to filter the results in a GridView according to search terms.  We can't pass txtSearch.Text as above, as this would invoke it immediately instead of informing the ODS to query the control every time it fetches the data (it would become a Parameter instead of SessionParameter). However, we can pass a special type of parameter which will be recognized by our expression parser:

ods.SetSelectMethod<ProductController>(
    ctrl => ctrl.FetchAll(    ODSHelper.Control<string>(() => txtSearch.Text),
                            String.Empty, 0, 0),
    ctrl => ctrl.GetCount(String.Empty));


The related method declaration for FetchAll is as follow:

public virtual ProductCollection FetchAll(string search, string sort, int startRowIndex, int maximumRows)


Here the main difference  is the call to “ODSHelper.Control<string>(() => txtSearch.Text)”  as the first parameter to FetchAll.  At this point, we have to remember that this is a fake call to our method FetchAll.  The method call ODSHelper.Control<string> takes a lambda representing which control and which property on this control should be used while constructing the ControlParameter that will be added to the SelectParameter collection. From the information contained in ODSHelper.Control<string>(() => txtSearch.Text), we can construct a ControlParameter as follows and insert it into the ODS SelectParameter collection:

var parameter = new ControlParameter("search", DbType.String, "txtSearch", "Text");


The first generic parameter T we pass in the method ODSHelper.Control<T> is simply the return type.  We want our lambda to compile even if it’s a fake call, so we simply make ODSHelper.Control<T> return an object of type T, which is the data type of the parameter on our SelectMethod (in this case a string).

Analyzing a Lambda Expression

From this point forward, the article raises the geekiness bar even higher, as we drill-down into how all of this is implemented in the background. If you are interested how we analyze the expression tree, continue to read.

Let’s look at what we are doing inside the extension method SetSelectMethod<T>(Expression<Action<T>> exp).  I strongly suggest you download the project at the end of the article to see all the code file at once, but I’ll paste and explain the most important parts here.

public static void SetSelectMethod<T>(this ObjectDataSource ds, Expression<Action<T>> exp, Expression<Action<T>> expCount)


This is the declaration of our extension method.  We are extending ObjectDataSource and we need an Expression for the select method and one for the GetCount method. Since we are using an Action<T> this means the lambda expression doesn’t need to return anything.  We shall now dissect this method in greater detail.

// Make sure we have a lambda expression in the right format
var lambda = (LambdaExpression)exp;
if (lambda.Body.NodeType != ExpressionType.Call)
    throw new InvalidOperationException("Expression must be a call expression.");
 
var call = (MethodCallExpression)lambda.Body;
 
// Our lambda needs to be a call to a method
if (call.Method == null)
    throw new InvalidOperationException("Expression must be a method reference.");

 

 
Those first lines simply make sure that our lambda expression is indeed a method call on an object and that we have a valid method too.  Just from the information we have so far from the expression tree, we can already set the TypeName and SelectMethod of the ODS:

ds.TypeName = call.Object.Type.FullName;
ds.SelectMethod = call.Method.Name;

 

 
The interesting part lies in the fact that we can analyze the parameters of the method call and extract parameters the ODS needs to make the call to SelectMethod.  The loop to analyze the parameters is quite big.  I’ll put it all here and explain what is happening directly in code comments.  The most important thing to understand before reading the code is that if we encounter a method call in one of the parameters, we have to check to see if it’s one of the special method call like ODSHelper.Control or ODSHelper.Session.  We do that by checking if the method have a special attribute applied to them.  If the attribute is not there, we simply execute the method so that it returns a value and we use this value as the default value of the parameter.  Here is the code for the loop:

int i = 0;
// Then from the method call reflection information, we can get the parameters
foreach (var item in call.Method.GetParameters())
{
    // Don't add any parameters that are standard fields like sort and paging fields
    if (ds.SortParameterName != item.Name
        && ds.StartRowIndexParameterName != item.Name
        && ds.MaximumRowsParameterName != item.Name)
    {
        // Get the part of the expression tree that represents this parameter
        var mexp = call.Arguments[i];
 
        DbType type = GetDbType(item);
 
        object val = String.Empty;
        
        Parameter param = null;
        Type attribute = null;
        string propertyName = String.Empty;
 
        if (mexp is ConstantExpression)
        {
            // If it's a constant expression (for example if the parameter is 0 or a string "test")
            ConstantExpression c = mexp as ConstantExpression;
            val = c.Value; // just use this value
        }
        else if (mexp is MethodCallExpression)
        {
            // The following if is a special case if we want our parameter to be a session parameter
        // We could add more special cases for ControlParameter etc.
            var m = mexp as MethodCallExpression;
            // Check to see if the method call has the Session attribute applied to it
            if (m.Method.GetCustomAttributes(false).Count<object>(y => y.GetType() == typeof(SessionAttribute)) > 0)
            {
                attribute = typeof(SessionAttribute);
                // Just get the session field name to construct the SessionParameter
                val = Execute<string>(m.Arguments[0]);
            }
            else if (m.Method.GetCustomAttributes(false).Count<object>(y => y.GetType() == typeof(ControlAttribute)) > 0)
            {
                attribute = typeof(ControlAttribute);
 
                // A method call to pass a ControlParameter looks like this:
                // ODSHelper.Control<string, string>(() => txtName.Text)
                // Argument[0] contains the lambda expression
                // We just have to get the object name and the property name
                // and we have the values required for our ControlParameter
 
                // Argument[0] contains the lambda () => txtName.Text
                // This is an UnaryExpression and we have to get the right
                // side of this expression which is txtName.Text and analyze it
                var unary = ((UnaryExpression)m.Arguments[0]).Operand;
 
                val = LambdaHelper.GetObjectID(unary);
                propertyName = LambdaHelper.GetProperty(unary);
            }
            else
            {
                // Here the expression is more complex, maybe we are calling something like a method or accessing a property
                // to get our value back.  To get the value of the parameter passed, we actually need to construct
                // a new lambda, compile it and execute it to get the value returned and passed to our method call.
                val = Execute<object>(mexp);
            }
        }
        else
            val = Execute<object>(mexp);
 
        if (val != null)
        {
            if (attribute == typeof(SessionAttribute))
                param = new SessionParameter(item.Name, type, val.ToString()); // val contains the session field name
            else if (attribute == typeof(ControlAttribute))
                param = new ControlParameter(item.Name, type, val.ToString(), propertyName); // val contains the control name
            else
                param = new Parameter(item.Name, type, val.ToString()); // val contains the value passed to the method
 
            // Here we make sure that the parameter is not already added in the collection.
            // If it's there but it doesn't have the same value, we have to remove and add it
            bool contain = ds.SelectParameters.Contains(item.Name);
            if (!contain)
                ds.SelectParameters.Add(param);
            else if (contain && !ds.SelectParameters.HasValue(item.Name, val.ToString()))
            {
                ds.SelectParameters.Remove(item.Name);
                ds.SelectParameters.Add(param);
            }
        }
    }
    i++;
}


I think the comments explain the code pretty well.  I suggest that you download the complete source code to see the code for other method calls.

SessionParameter and ControlParameter

I want to get back to the syntax I chose to specify a parameter shall be a SessionParameter, a ControlParameter or simply a plain old Parameter.  We are using two static methods from ODSHelper class:

[Session]
public static T Session<T>(string sessionFieldName)
{
    return default(T);
}
 
[Control]
public static T Control<T>(Expression<Func<object>> action)
{
    return default(T);
}

 
Basically those methods do nothing.  They only return a default value of type T.  Why are we doing that?  We need to satisfy the parameter type of the method call where they will be used.

Why is it important to return T and not simply return a string?  For example, take the following method to be used as an ODS SelectMethod:

public TestCollection FetchAll(DateTime date, string search, string user, string sort, int startRowIndex, int maximumRows)

 
You want to pass this method with SetSelectMethod<T>.  The main difference is the first parameter.  It’ll take its value from the Session. Session[“FieldName”] will return a DateTime.  Here is how we could do it:

ObjectDataSource src = MainDataSource as ObjectDataSource;
 
src.SetSelectMethod<ItemGroupController>(
ctrl => ctrl.FetchAll(
ODSHelper.Session<DateTime>("FieldName"),
ODSHelper.Session<string>(SharedSessionVariables.GetSearchField(Page)),
HttpContext.Current.User.Identity.Name, 
String.Empty, 0, 0))

 
Pay attention to the first parameter.  What is important is that our method compiles and that our expression analyzer has enough information to fill the SessionParameter properties.  When we analyze the expression tree, we’ll be able to look at the parameter passed to the method call Session and see the Session field name.

The second really important characteristic is the custom attributes that both methods have ([Session] and [Control]).  Those unique attributes are used when we walk the expression tree to know what kind of parameter we need to create.  Without them, we could not distinguish between normal method calls and those that are here to give us information in the expression tree.

SessionParameter

ODSHelper.Session<string>(SharedSessionVariables.CurrentProductAuthority)

 

or

ODSHelper.Session<string>("CurrentProductAuthority")

 
If you don’t have a Session wrapper that defines the constant session field names, you can use the second option, which is more direct. When we analyze the expression tree, we simply evaluate the expression inside and create a SessionParameter with the result of the evaluation as the session field name.

ControlParameter

ODSHelper.Control<string>(() => txtName.Text)

 
This is how we pass in a control parameter.  Once again, we have a direct reference on the control and its property. If we remove the control later, our app won’t compile. txtName is this context is simply a TextBox that we have in our page.

ControlParameter needs two main things, the name of the control and the name of the property of the control.  We can get those two things by analyzing the expression tree of the passed lambda expression and add our ControlParameter in the parameter collection accordingly.

Parameter

For parameter inside our lambda expression that we pass, for example:

HttpContext.Current.User.Identity.Name

 
The expression analyzer simply executes this code and sets the returned value as the default value of the parameter.

Other Parameter Types

Right now my code is only supports those three kinds of parameters, but it would be pretty easy to add the other types of parameters (Query, Cookie, etc) with a little bit more work. Furthermore, the same logic could apply to the Update/Insert/Delete methods used by the ODS.

Conclusion

We need your opinion!  I think this project could be pretty useful.  The syntax is not very elegant, but I can't see a better way of doing this at this point.  Please download the project, play with the code, and give us your feedback! Ideally, this is something that would be built into the framework at a later date, but in the meantime we can still play around with it! This might not be the only way to solve the issues we are tackling, and if you have any other suggestions, let us know! (Example: Extending the ODS for each controller?)

Example Page with Northwind | Download Source

kick it on DotNetKicks.com



Software usability is like eggnog

clock November 27, 2008 01:15 by author JKealey

Yesterday, I had the chance to participate in a formal usability study at the University of Ottawa. Marconi Lanna’s master’s project is an improved code difference visualization tool. I had already played with the tool in the past and found it interesting but only saw its true value during the formal usability test where I was given specific tasks to perform. I will keep the details about this tool for a later post when it becomes publicly available, but participating in the study helped me realize a few things:

  1. Creating a proper usability study is hard.
    • There are so many ways you can introduce bias in a test, it isn't funny. For this particular test, I feel that it was very close to being unbiased and most of my initial concerns were addressed.
    • One concern that does remain is the fact that the test cases were crafted by the researcher and could have been manipulated to generate a specific set of results. However, crafted test cases are required for any kind of comparison between the users. I personally feel the test was unbiased as it presented potential flaws in both the default tool and the proposed tool.
    • This particular test was for a tool used by software developers. As you probably know, some developers demonstrate productivity levels an order of magnitude higher than others.  You can imagine that this makes it hard to analyze productivity results collected during a study.
  2. Don't forget to make the test subjects communicate
    • If a subject does a task and says nothing, you don't learn 10% of what you could have learned about their thought process. In usability studies, the thought process is the most important thing to understand.
    • Everyone is different, and having them express their concerns/procedures helps you analyze the results.
    • This is not a discussion. Because of the desire for unbiased results during the questions, you cannot ask specific questions (which may lead to bias) or comment on the answers (which will definitely lead to bias). However, after everything is done and recorded, having discussions with the subject can enhance your overall understanding of what they said while thinking aloud.
  3. Working on usability is a self-improvement task that you must work on every day.
    • It's easy to continuously postpone enhancements to the the non-functional aspects (performance, security, reliability, usability, etc.) of a software system when you're a small startup.
    • You should log even seemingly superficial non-functional concerns in your bug database just as with any other issue. Customers that complain are rare and you must take their comments seriously.

Following my own advice, I want to look at one of the usability issues that I filed in our bug system a few months ago, our bug #1377. I like revealing some internal details of the issues we face and appreciate your input on our strategies. Note: if you're not interested in a real-world example at a complex usability issue, skip to the conclusion of this post as this could be a snooze-fest!

A closer look at one of our usability issues

Drink too much eggnog and you too will have usability issues Usability is one of our core values when building software. However, we sometimes slip up, as with anything! Looking back at the comments received over the last year by our users, I can clearly identify the single most important usability problem we have with our wide range of software solutions for the franchise industry. The issue is related to our party/event/appointment booking module which is a part of our franchise point of sale (POS) software. I'd like to drill down into this feature and present the usability issue and how it came to surface.

Background information

A POS system basically lets you sell items in a retail store. Our is integrated with a frequent buyer program, and we register information concerning each member in the database. Depending on the franchise brand, different information is tracked. (We can do cool things when customers are willing to provide information such as their postal or email address, but as a general rule, all fields are optional). We also have a party booking module which allows parents to book their child's birthday party at a store. The feature accomplishes two things: it acts as a calendar system (to avoid booking overlaps) and is integrated with the sale module to support deposits. Obviously, when someone books an event at a store and pays a deposit, the system needs to remember who paid what, so that when the big day comes, the deposit can be applied to the transaction.

To make a long story short, we offer a number of configuration options in the background to support different scenarios used by different franchises. Here are some examples:

  • Book a child birthday party in one of multiple rooms.  You reserve the room for a period of time for your exclusive use.
    • This is akin to booking a meeting room.
  • Book a space in one of multiple play areas. You reserve your spot in the play room, but the room is shared up to its capacity.
  • Book a particular employee for your haircut or spa services.
    • Rooms are not the only resource that can be booked. This ties in to the employee management system.
  • Book a table, a pitcher of beer, and that hot new employee to serve at your table.
  • You're booking a particular resource and pay a: 
    • Flat fee (party package)
    • Flat fee per guest
    • The fee will depend on what is bought at a later date.
  • Stores have the possibility to ask for a deposit, and if they do, it can be of a particular % of the transaction or a fixed dollar amount.
    • The store decides if deposits are refundable. (in the event of a cancellation).

As you can see, there are numerous elements at play here that make it non-trivial in our context, where we design the booking system to meet each franchise's particular booking needs. What is important at a high-level, however, is we defined the following use cases when researching the requirements for this module.

  1. (Deposit is required) A member is in the store and wants to book an event.
  2. (Deposit is required) A member is on the phone, wants to book and event and will come by later this week to pay for the deposit.
  3. (Deposit is not required) A member wants to create a booking.
  4. Employees want to peruse the list of bookings for a particular date range.
  5. Employees want to add an additional deposit amount to a transaction (regardless of the fact that deposits are required or not)
  6. Employees want to finalize an event, and apply the total deposit amounts to the transaction.
  7. An employee books a "fairy princess" birthday party for another employee, without their knowledge or consent, and invites their friends and family. The birthday boy frantically tries to cancel the event as soon as possible, to avoid long-term trauma.
  8. (and a few more, less important scenarios).

We chose to divide our scenarios contextually

We decided that the best way to solve this problem was to create two ways to access the booking information, one which is contextually associated to a store member and one for higher-level tasks.

  1. From the existing sales page in the POS (for all scenarios that work with money)
    • The cashier first selects a member from the frequent member program (or create a new account)
    • The system displays a new button called bookings
    • The cashier selects bookings and progresses through the following menus (create a new booking, additional deposit, cancellation, finalize event).
    • The system then returns back to the sale page, where the cashier can process the sale/refund.
  2. From the events page in the POS (for all scenarios that don't deal with money)
    • The system displays an Outlook-like calendar of all the events, allowing employees to peruse the events
    • Employees can view availabilities, change rooms, change employees, change date&time, enter additional event information from this page.
    • Employees can create an event without a deposit, which the system presents as being in the "Deposit Required" state.
      • This is to support scenario #2 listed above. You'd create a tentative booking, which you'd be willing to double book if the person never comes by to pay for the deposit.
      • Also, this is used by franchises that don't require deposits instead of going to the sale page, and becomes similar to a paper calendar in a hair salon.

The usability problem

After being in use for about 18 months, I can state that we need to step back and re-evaluate this feature, to improve its usability. The system works fine when used as intended, but I have observed the following issues:

  • If deposits are not required, untrained cashiers book events in the event page and never finalize them.
    • They do open up the sale page, possibly select the member, and sell whatever was bought without associating the sale with the booking.
    • This is bad because we're left with open events and can't track cancellation rates, sales per member, etc.  (bad for the store)
  • If deposits are required, untrained cashiers assume they need to go to the events module to do anything related to events.
    • They book an event, but the system marks it as "Deposit Required" and doesn't let them change the deposit amount.
    • This is bad as they are under-using the system, not following store policies, and sending in support requests to have the system explained to them. (bad for the store and bad for LavaBlast)

As a side note, I am honestly always surprised when a cashier expects to book an event, accept money but not track any information to associate the deposit with the person making the payment. That's like their store manager expecting them to come in to work and not bothering to write their name on their timesheet.

Root cause

  • We've divided the scenarios in two groups: the ones that deal with money and the ones that do not.
  • Combine this with the following facts:
    • Some stores don't use deposits. Therefore, money is only involved when finalizing the event at which point the main task is selling products/services, not dealing with events.
    • Even if we use deposits, we allow the creation of temporary bookings (in the deposit required state).

To recap, to improve usability, we've defined processes that contextually lead the cashier to performing their tasks. However, since the core starting point (deposit amounts) varies completely from one franchise store to another, our contextual division doesn't make sense.

Possible Solutions

  • Possible solutionsSolution #1: In the store events page, after selecting an event, give the option to the cashier to add a deposit, cancel or finalize the event. This would bring the user from the events page to the sale page and allow them to follow our defined processes.
    • Possible issue: We're making them switch contexts implicitly. When they're done, they would wonder why there were in the sale module instead of the events module. (do we force them back to the events module when they are done?)
    • Possible issue: they want to finalize an event that has already occurred (to get rid of "open" events, caused by the described usability issue).
  • Solution #2: In the sale page, make the booking button available even when no member has been selected. Once clicked, prompt to select a member or offer to peruse the list of bookings. Selecting an event would auto-select the member, and bring us to the usual process.
    • Can be combined with the previous solution.
    • Possible issue: This allows cashiers to do anything at any time while in the sale module and loses the contextual value of the process we've defined. One main problem in the POS industry is the constant desire to put all options on the page, flooding the user with too many options and making the POS hard to use and increases training costs.
  • Solution #3: Do the previous solutions and get rid of the top level events page.
    • If everything can be done from the sale module (and in most cases the sale module is where the cashier spends most of their day), why bother having a separate mode, that adds confusion.
    • Possible issue: cashiers won't instinctively feel that the sale page is where they want to go for event bookings, they might not even discover that events exist!
  • Solution #4: Cut the feature set to attack the root causes directly
    • If deposits are required, get rid of the feature that allows us to create temporary bookings.
      • Possible issue: people will complain that they can't do this anymore. As deposits are required, this is a case of tough love.
    • If deposits are never required, don't integrate with the sale module at all.
      • Possible issue: decrease the perceived value of the event module and lose a few potential reports (which are not in use at this point).

At this point, we'll probably end up implementing #1, #2, and #4. One of our core technological themes is usability, therefore we're willing to live with cutting a few features for an improved process. However, the issue is still up for discussion and I'd love to hear your opinions on this specific matter or on similar problems you've experience in your software startups.

Conclusion

We've built a flexible core to support the varied needs of franchisors. Everything works, but are we offering too many features to our customers? Should we cut back to make it simpler on everyone? In our case, I think so. If I had to sum up the usability problem discussed today into a high level lesson learned, I'd state the following: Don't separate use case scenarios into different groups if a configuration option invalidates the grouping premise.

During this holiday season, I think I'll take some time to re-read a few of my favorite books on usability:

  1. Steve Krug - Don't Make Me Think
  2. Joel Spolsky - User Interface Design for Programmers

Although these are great reads, they deal with the low-level user interface design problems, not with higher-level software engineering feature interaction problems in software product lines. I'd be curious to know if there any good resources on this subject (other than overly academic conference papers/workshops). I'd love to hear more first-hand, real-world experiences from other bloggers.

Easy to use software is like eggnog.

  • Reaching the desired results takes time. (The best eggnog takes at least three weeks to make).
  • Users will get a kick out of it. (With as much bourbon as milk, eggnog will definitely get people talking.)
  • At a high level, anyone can claim to be a pro. However, the devil is in the details.
  • You need to listen to your users to make the recipe better in the future.
  • What would the holiday season be without it? (okay, that doesn't make sense!)
kick it on DotNetKicks.com


LavaBlast Software named Startup of the Day by Microsoft

clock November 15, 2008 10:37 by author JKealey

FLY-002_StartupZone_Badge-CompanyOfTheDay_IR5a We recently joined the Microsoft BizSpark program after seeing posts about it on a few blogs (Flow Ventures and The Code Factory). To make a long story short, amongst the hundreds of startups that register each day from around the world, we've been selected as the most promising startup of the day. It is always fun to get recognition for our hard work!

In any case, you can view the interview here (today only!).

Have a nice weekend!



Gotcha: ASP.NET and exceptions in asynchronous tasks

clock October 30, 2008 13:00 by author JKealey

Awesomeness Here's a little piece of information you might not know about how ASP.NET 2.0 and above operate. Simply put, if an unhandled exception occurs in a secondary thread which has been launched by your ASP.NET application, IIS will force your application to restart. This can mean lost sessions, bad server side state, slow reloads and, in general, is a bad thing. However, these kinds of exceptions are a pain to discover and resolve if you don't know what you're looking for because the built-in ASP.NET error handler does not get fired and your user is not redirected to an error page, unless something breaks after the web server restarts. (This article explains the cryptic messages you will see in the Event Log).

Here's a simple scenario based on the integration between our interactive kiosk created for The Code Factory co-working space and Twitter. We want our web application to post messages on Twitter when certain events occur (member check-in and member check out). Because we did not want to re-invent the wheel, we found a Twitter C# Library with a permissive software license. Noticing that posting to Twitter slowed down our application and because we didn't want our application to depend on Twitter's availability, we decided to run this code asynchronously. Being careless, we supposed that should anything bad occur in this second thread (timeouts, invalid login/password, etc.), the system will simply not be able to post, which was not a problem for us. We were wrong, and the whole web application restarted because of this. (This was not the case in ASP.NET 1.x and was previously discussed by others).

In any case, here's some sample code to run the Twitter library asynchronously, including a try/catch.

using System;
using System.Collections.Generic;
using System.Text;
 
namespace LavaBlast.Util.Twitter
{
    public class AsyncTwitter
    {
        public void Update(string userName, string password, string status)
        {
            TwitterDelegate caller = new TwitterDelegate(UpdateTwitter);
            caller.BeginInvoke(userName, password, status, new AsyncCallback(CallbackMethod), caller);
        }
 
        protected delegate void TwitterDelegate(string userName, string password, string status);
        protected static void CallbackMethod(IAsyncResult ar)
        {
            TwitterDelegate caller = (TwitterDelegate)ar.AsyncState;
            caller.EndInvoke(ar);
        }
        protected static void UpdateTwitter(string userName, string password, string status)
        {
            try
            {
                Twitter t = new Twitter();
                t.Update(userName, password, status, Twitter.OutputFormatType.XML);
            }
            catch (Exception ex)
            {
                LavaBlast.ElectronicJournal.Error("Unable to post to twitter.", ex);
                // ignore. 
            }
        }
    }
}

The lesson learned is unhandled exceptions in other threads can wreak havoc on your ASP.NET application. To help debug these kinds of errors when they occur, I do suggest you setup the HttpModule developed by Peter A. Bromberg that adds the actual exception in your Event Log. Peter describes the problem in more detail than I do, and is worth a read.

Have a Happy Halloween! (Halloween will be weird for us in 2008 because we were hit by the year's first snowstorm on Tuesday.)

halloween

kick it on DotNetKicks.com


Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in