Tuesday, September 14, 2010

Dilbert: Social Media age

I cannot resist to re-post this strip!


Dilbert.com


Are such attitudes only due to managers that need to keep a straight control over their reports in order to seamlessly justify their position? I already covered that aspect in my blog post Manager Attitude back in Dec. 2008. I then stated that the situation would be smoother is such managers could fully assume their facilitator role...

These days, I think it's more related to the difference in each person's cognitive age!

In the traditional chronological order, you could become a manager because you knew the company internal mechanics, the management dynamic, and its long term vision. People finger-pointing the Generation Y (see my post Work around the general laziness, for example) have good reasons to ask new employees to first acquire good experiences before giving them rewards and responsibilities.

However, with the emergence of multi-stream platforms, young people have a tremendous possibilities to learn so much in short periods of time and to overpass older ones. My involvement in edu.cyn.in proved this statement many times: kids are much more prolific at producing online content and sharing with their peers than their own teachers!

On the other end of the spectrum, a lot of retired people who don't have to compete again to maintain their social rank, have gain the possibility to become relevant again by being more connected with younger crowds, more socially involved.

This Dilbert strip illustrates a direct confrontation of the socially aged person and the just chronologically aged one!

I hope you like it too.
A+, Dom

Monday, September 6, 2010

Update business-side

It's been a long time since my last post. The summer went by so quickly. With my partner Steven Milstein, we have focused on building a generic tool for golfers and golf courses. This post summarizes the latest developments.

The product name

The product name Twetailer is now AnotherSocialEconomy.com. Twetailer was chosen when Twitter started to become mainstream and was made of a combination of Twitter and Retailer. The project Dretailer (more in a future post) was name from Android and Retailer, and we thought about having a variation per connector type.

AnotherSocialEconomy.com is better because it really illustrates that our offer is about a business platform in a global economy, with social networks involved, in a new way of connecting people—and it is not tinted by any service du jour. More information on Steven's blog post The Twouble with Twetailer.

ezToff.com: a dedicated implementation

AnotherSocialEconomy.com provides a generic multi-channel communication engine, conveying messages among consumers and retailers according to the given location and some search criteria. Whenever we explained what's the product is about, potential consumers understood but were blocked when it was time to formulate one of their needs... We knew then we needed to offer a very simple interface where users can post demands without thinking twice about it!

At one point, we met Marc Bienstock who offered to help us building such an interface for golfers! According to Marc:
  • When a golfer has a chance to play (because his wife gave him the permission ;), he usually calls his buddies to find three of them who are free too.
  • Then he has to call a golf courses to book a round at the agreed on time, maybe with one or many pull carts, one or two golf carts, etc.
  • If the booking goes well, he calls his buddies to give them the golf course coordinates.
  • If it does not work, he calls another club or calls his buddies with the time of the available rounds.
  • Way too many phone calls to be able to spend a minimum of $200 for 4 players!
  • On the golf course-side, they have to deal with so many phone calls; some of them have a system queuing calls up to 50!
  • Average time spent on the phone per caller is too long, and many are useless because they cannot screen them.
  • When a round stays free, it's an average of $200 lost.
So end-of-July, we launched ezToff.com which allows:
  1. One golfer can submit a request for a tee-off from one central place and it will be broadcasted to all participating golf courses around the given location. The golfer can specify the e-mail addresses of his buddies so they'll be cc'ed for all exchanged messages.
  2. Golf courses are provided a Web console which displays all requests posted in their area. According to their schedule, they can propose rounds—the price per round and the total cost should be documented. Golf courses don't have to watch the console indefinitely as ezToff.com can notify them by e-mail, SMS or tweet.
  3. Proposals are sent back to request initiator by e-mail (his buddies receive a copy if he gave their e-mail addresses). At this step, the golfer can wait for more proposals to come. If he wants to book it, a simple link in the e-mail will generate a response to be sent by e-mail to ezToff.com.
With ezToff.com, golfers can get tee-off proposals with just few clicks. Golf courses get all demands electronically and can focus on the ones they can propose a round to (others are simply declined). Buddies of the golfers are notified at each step of the process automatically. No more infinite phone calls ;)

We interviewed many golfers and they confirmed the golfer's pain. With some visits to golf courses, we've confirmed the courses' pain too. The difficulty we have is that we're not sales people and it's hard to close a deal with golf courses... Knowing that the season is almost finished here, that our business model is probably too expensive for the golf courses, that we need good marketing materials, we're going to tune the offer and be ready for the 2011 season.

The AnotherSocialEconomy.com reseller and influencer programs

The ezToff.com project has been beneficial for us on many points:
  • With the widget, we have another way to reach consumers: influencers (bloggers, Facebook groups, associations' site, for example) can embed it into their webpages.
  • If dedicated Web consoles are available to golfers and golf courses, all operations can be done with e-mails, which is the most pervasive communication tool.
  • The original messages produced by the engine were short to accommodate the Twitter limitation of 140 characters per message. These ones were cryptic to too many people. The variation of these messages for e-mail are now much friendlier and contains links ready to forward the readers' response.
  • We'll simplify the pricing model thanks to the received feedback.
But the most significant development is the offer of the influencer program:
  • Each influencer will receive 25% of the revenues generated by consumers confirming demands posted with their copies of the widget.
  • An influencer can propose one or many widgets anywhere on the Web he reaches his/her community.
  • We'll work with influencers to customize the widgets for his/her community.
In parallel, we developed the reseller program to share 25% of the revenues generated by retailers proposing goods or services with AnotherSocialEconomy.com.

It's possible that some well organized enterprises will be their own reseller (cut of 25%) and will drive requests with their copies of the widget (another cut of 25%) but that's fine. We'll make some money if they make some, and they'll have a good discount if they help driving more traffic (which means more business to them).

Next steps

On business side, Steven and I are looking for resellers that will help to bring ezToff.com to the next level. We are also trying to develop another domain specific implementation, another skin on the top of the AnotherSocialEconomy.com engine.

On the technical side, if everything works perfectly for the ezToff.com users, I need to document the widget usage and the REST API used by the Web consoles. I need also to enable the Facebook connector for influencers to communicate on this platform as they can do on Twitter. There's also the Android application (project dretailer mentioned above) to update to benefit from the Android Cloud to Device Messaging (c2dm) mechanism and then to push notifications asynchronously. Still a lot to do but no road block ;)

A+, Dom

Monday, July 19, 2010

How to resize VirtualBox disk images?

-- Update on May 8, 2012 --

Having to resize an image again, I looked at the VirtualBox documentation before following the CloneVDI route a second time. And I was pleasantly surprised that the version 4.1 of VBoxManage accepts a parameter --resize to the command modifyhd!

The process can be done in a matter of minutes:
  1. Stop the hosted system (Win7 in my case).
  2. Run the following command in the folder of your VDI file
       VBoxManage modifyhd <name>.vdi --resize <size-in-mb>
  3. Start the virtual machine.
  4. Open the disk manager tool (Use the Menu Windows, type disk man in the search box, and select 'Create and format hard disk partitions').
  5. You should see your drive with the initial partition(s) and new free space.
  6. Click on the partition to extend and choose the command 'Extend Volume' in the contextual menu.
  7. And voilà.
No need to copy the VDI on the host machine. Very fast and robust process.
'Extend Volume' option in the contextual menu.


-- Original post on July 19, 2010 --

Quick post to share a wonderful VirtualBox companion: CloneVDI!

Almost two months ago, I switched from Windows XP to GNU/Linux with Ubuntu 10.04 distribution. Everything goes very well and I do not regret the move.

In my day-to-day job, as the architect/developer/tester of the Twetailer project, engine and of many of its clients, I still need to run programs on the Windows OS, especially the series of Internet Explorer 7 & 8 (IE 6 is killed, isn't it?).

To verify my test suites for Internet Explorer, I rely on VirtualBox running the initial Windows XP release. Because the disk size requirement for the initial version is low, I stupidly created a small  4 GB disk image! Then it required hours to load and install the service packs 2 & 3, plus IE 8, plus the latest .Net framework, plus the security updates. Be careful: 4 GB is way too small to store the system, the virtual memory page file, and the additional stock coming with the service packs and others!

Few days after the initial setup, I was facing the "Not enough space available" warning :-( Instead of wasting another set of hours in a re-installation, I googled virtualbox increase vdi size, and the first article I found was from the VirtualBox forums. I thought it was a good sign and I started reading... but I became quickly disappointed because the given explanations required many tricks and time to setup too. Then I jumped to the last pages (page 6 to be precise) to read:
...

The new way:
Run the CloneVDI tool. Enter name of source VDI and desired disk size into dialog box. Click "Proceed" button. Expect to spend maybe 5 minutes for a typical VDI, longer of course with big drives.

The CloneVDI tool has existed since mid September 2009. It was created specifically so as to remove the need to recommend an embarassingly complicated rigmarole for performing what should have been a simple task. So, in late May 2010 it is quite disheartening to see people still joining the site in order to provide uninformed endorsement of the obsolete procedure.
The post was from Don Milne, alias mpack, the creator of the CloneVDI tool.

Then I loaded CloneVDI from the referenced post, installed Wine with the Ubuntu Software Center, ran CloneVDI, selected my initial image, specified a new name and a new size (now 10 GB), and all the magic transformation occurred in less than 1 minute!

CloneVDI pane: clone an virtual image with an increased size in just a few clicks!

Warning: It seems the cloning only works up to the first snapshot, as my clone did not get the snapshots. This was not a issue for me but be careful on your side because it might be necessary to merge the snapshots. As the cloning is very fast, producing a new image per snapshot should work-around the issue.

Anyway, I wholeheartedly recommend CloneVDI when it's time to allocate more disk space to a VirtualBox machine!

A+, Dom

Monday, June 7, 2010

The joy of Ubuntu

Few decades ago, most of my work was done on a SUN hardware running Solaris. The Université de Rennes I, France, was providing the Internet access and Mosaic, followed few times later by Netscape, was my favorite browser. At that time, I was preparing a PhD and most of the online discussions were conducted in newsgroups and emails.

One week ago, I definitively switched the OS of my Thinkpad T61 from Windows XP to Ubuntu Lucid Lynx (10.04). In some ways, the Ubuntu environment is not that different from the Solaris I used to work on. For example, I was very pleased to use again an efficient Virtual Desktop system and to benefit from the native Compose key mechanism that allows me to type any crazy sequences producing foreign characters.

Having been pushed on the Microsoft side by my various employers, it seems I forgot all the good sides of the Unix environments. Microsoft marketing has been very brilliant: as many others, I accepted the PC environment limitations! Do you remember the "cooperative multitasking" of Windows 3.1? Do you remember that changing a registry key transformed your Windows NT Workstation in a much capable Windows NT Server? Do you remember that Windows 7 is really the first shiny interface with transparency and gadgets? (Sorry: Vis-what? I don't know ;)

To be honest, let's recognize that the Unix environments did not progress much. On the open source side, the Linux distributions provide a very fragmented offer. The success of some shiny distributions like Knoppix and Ubuntu is fairly recent.

As a geek, I would say that the most impressive feature in these distributions is the 3D rendering engine Compiz and its Cube plugin! It's just amazing.





When I decided to switch to Ubuntu, I wondered if I would lose some features. So far, all have their counterpart for the Linux environment, sometimes with many additional benefits. I did not have to even install Wine, the Windows emulator! Here are the tools that were important to me and I carried over:
  • Dropbox: has Linux/MacOS/Windows distributions.
  • Keepass: had to convert the database to 1.x format and now use KeepassX which has Linux/MacOS/Windows distributions.
  • Aptana/Android SDK/App Engine SDK: equivalent Java packages.
  • Firefox/Chrome/Add-ons: have Linux/MacOS/Windows distributions.
  • Skype (for VOIP calls and screen shares)...
  • OpenOffice.
  • VirtualBox.
  • git.

I am so impressed with the system that I've also switched my old Toshiba M40 (one P4 core and a NVidia card) and now my kids have an easy access to many free games and educational tools!

A+, Dom

Friday, May 28, 2010

What motivates us?

One year and half ago, I wrote the blog post Career Advice '08. The key idea was about encouraging people to develop their own expertise to be successful in their career. Don't wait for your management to give you something exciting, do it yourself!

Two weeks ago, I wrote another blog post Work around the general laziness. In some ways, I reported on my disappointment of not seeing enough autonomous experts, and that entrepreneurs have to compose with variously skilled teams.

I finished my second expose by mentioning the Twetailer's work-for-attribution offer. I explained that many people liked the concept and that few of them have committed to deliver something with their own schedule. Each one at its own pace builds an extended skill set.

What are the commonalities between the contributors?
  • They have a regular job so the money is not an issue.
  • They are performers and have a high level of satisfaction.
I was such a contributor when I started working with Steven, before jumping on the entrepreneur-side to help developing the business from the inside.

I had not really identified the sources of my motivation until I saw Dan Pink's illustration of "What motivates us", for a talk given by the Royal Society for the encouragement of Arts, Manufactures and Commerce (theRSA.org).
The three factors that lead to performance and personal satisfaction:
  • Autonomy
  • Mastery
  • Purpose.





I strongly encourage you to look at that video, part for the fun of viewing Dan Pink in action, part for the delivered message. I encourage you also to share your experience as a comment below ;)

A+, Dom

Tuesday, May 18, 2010

Work around the general laziness ;)

This blog entry is primarily inspired by a rant from Jason Calacanis, during the episode #47 of This Week in Startups (full show, or directly starting near 5:20). During that show, Jason goes against the Generation Y people who are usually lazy and want everything before actually delivering...

Another source of inspiration is Mark Suster, the co-host of This Week in VentureCapital, with a series of posts culminating with The Long-Term Value of Loyalty.

What's their point-of-view?

Jason is mostly focusing on a group of people, a group that has been educated with a different mindset from his own one. Gen-Y people have more concerns about ecology in general, for example, they are used to getting free or cheap digital goods, and many of them live with their parents longer than before...

If I can agree on the fact that many Gen-Y people seem lazier than the the Gen-X or the baby boomers at the same age, I think the key differentiators is their relationship with money: the Gen-Y people think they need less money, so they don't work that hard to get always more!

In his first post of the series, Mark pinpoints job hoppers, employees who can resign anytime, without much respect for their commitment or for the situation of the company. If Mark readjusts the context in the second post by writing that “quitting a job because it's a mess is OK”, Mark still thinks that being “loyal” is important and everyone should stay loyal for long term benefits...

I can agree with Mark that working with such individuals is risky, that developing a business with not-so-loyal people is tough. However, I think that building a business with people from different mindsets and backgrounds is more valuable. To me, diversity, if you can manage it, is more important than loyalty!

Is laziness a syndrome limited to Generation Y?

Before becoming an entrepreneur, I was an employee like many others. In France, I worked for a very small company and then for a multinational corporation before immigrating to Canada. My first job here was for a medium Montreal company which was later bought by Oracle. I quitted Oracle to go with IBM Rational, which I quitted to go with Compuware. In addition to my professional life, I've been involved in many non governmental organizations.

Along my life, I've always been curious, interested in learning new subjects, debating around them, and trying to implement the best ideas, especially when I'm passionate. I've been lucky to team up with great people (note that I learned a lot too from the failed partnerships) and I've gotten excellent mentors. When I write “lucky”, I mean “I worked hard to consider myself as lucky.”

All in all, I can honestly say I haven't seen a lot of people working very hard at work, not that they are lazy, just that they have different priorities, different motivations. Although I've already proposed many times to be a mentor, to give lunch-n-learn talks, to organize specific trainings, etc., -on my own time, for the only benefit of the recipient- only few people have followed.

I've observed the “good enough” attitude with any types of people: old or young, men or women, immigrants or native country, Europeans or Americans, etc. In Western countries, we don't have to fight for a shelter or to find food, so the sense of urgency is blunted. Why would people with already enough (enough money, enough responsibilities, enough social involvement, etc.) go for more?

Maybe the Gen-Y people are worse than the others,because their “good enough” level is lower than before. IMHO, they are just like the common crowd, just normal people in our modern world.

How to deal with the general laziness?

As mentioned before, I think the diversity is important. Trying to stay in closed vacuum with the elite can help a bit, but not for a long time. The key point is to compose teams with top elements and less skilled ones. The newbies can learn from the experienced people and more help around them will reduce the “single point of failure” risk.

For sure, you cannot reward the in-learning people as you do reward the top team members. As I explain to my kids, there's a consequence to everything! If you work hard, if your help is valuable, if you go beyond your tasks, the system should reward you accordingly. If it does not come immediately, it should be clear that it will come eventually if everything goes well. If the hard workers don't get a tangible ROI, I think it's normal to expect them to slow down or to quit... (to expect them slowing down ?)

To me, the key factor in a successful project is the commitment of the participants. It's not that important that this intern has a lower velocity than an experienced engineer because we can plan accordingly. What's more important is that you trust that he's going to deliver as expected, or if he has some troubles that he's going to report them as soon as possible.

In the case of software developers evolving in an Agile environment, measuring their work progress is not really an issue. If the team is correctly equipped, everyone is accountable. In the peripheral teams (product managers, marketing, sales representatives, etc.), commitments are more difficult to get, and the more sources of non-productivity there are, the fewer chances of success you get.

A real case, please!

My partner Steven and I have developed the concept of work for attribution. Because we're a startup, we cannot offer salaries in exchange to work. However, we offer to attribute back the work to the ones who have delivered it. Immediately, contributors can use our environment as a lab to test and develop new ideas. We have a working product and it's up to them to adapt it, to make it better.

If our projects are successful, if we can cash them at one point, the contributors will be part of the success and then we'll try to reward them. If a big player wants to acquire us, top contributors will be probably part of the deal.

So far, many people have liked the concept and a few of them have committed to deliver something specific. Some of them are Gen-Y people, some are immigrants, and everyone work at different rates. If I can rely on people I trust, on people who are going to deliver what they have committed to, whoever they are, it's very cool!

A+, Dom

Wednesday, April 21, 2010

Social Software, Cynapse, and Open Platforms

What's a Social Software?

These days, most of white collar workers have to deal with computers on a daily basis. Computers are everywhere: from the front desk to allow receptionists to get to the company directory up to garage doors checking who's in, who's out. There are so different types of computers that some of them go unnoticed!

The scope of this post is limited computers used to collaborate:
  • Make appointments
  • Produce documents
  • Review & comment documents
  • Forward documents
  • Poll users
  • Manage tasks
When it's time to collaborate, the vast majority of computer users rely on e-mails. E-mail is probably the most essential tool now, and without e-mails, many are lost. Do you remember the BlackBerry syndrome of the RIM product early adopters? Yeah, the one that wakes up people middle of the night so they can get new e-mails ;)

With tools like Microsoft Outlook, Apple iCal, and Google Calendar, more users relying on calendar tools to organize their time. This is neat because with such tools you can sometimes see up front if the targeted time slot is good for other attendees. And when attendance confirmation comes, the meeting information are updated automatically without requiring a specific triage in the mailbox.

When people have to share pictures, because the ones modern digital camera can generate are so big and very few people have the knowledge to reduce their size nicely, more and more people resort to online hosting services like flickr or Picasa. In addition to process  images to transit on the Web (while still available in high resolution for printing purposes), sharing pictures via links is way lighter for the mail system. It has also the additional benefit that pictures can be removed safely without one someone else to purge his/her mailbox.

With review sites like cnet or Epinions.com, more and more users have started to give their opinions online to share the joy of being a owner of a wonderful gadget, or to inform others that such a gadget is crap! Involving readers and customers to share their experiences is the key element of the social software movement.

So what's the relation between Cynapse and social software:
  • Cynapse is a central place where people can collaborate online with the most practical tools without relying on e-mails, a place where collaboration and communication do not loose their context.
Why Cynapse?

When my partner Steven and I started to work on the !twetailer, we knew that we wanted to collaborate online, within a reliable and protected environment that could handle many document types: user stories, specifications, diagrams, mock ups, pictures, bookmarks, discussions, etc.

As IBM-ers, we looked among the tremendous IBM products and we selected Lotus GreenHouse which was still in beta. The main flaw we experienced was its disruptive slowness. Another issue to us was our quasi-impossibility to influence the development path to fix the painful issues. Just 2 guys in a big user community have a very little impact...

We then decided to switch to Elgg, a free and open source software, that our hosting service offered. Elgg is probably a good tool but it did not match our needs. The variety of entities was limited as our ability to fix the issues. Probably, it would have been better to host the service at home and to extend its model. Because it would have distracted us from our project, we looked for a better solution.

At this time, Steven was studying the social software offering and Cynapse came out as a good fit to us:
  • The service out-of-the-box was promising and was still under active development.
  • They had an affordable offer: free self hosted service, managed service for a small subscription, managed hardware also for a fee.
  • They had an active user community which was hosted on the tool itself (the “eat your own dog food” principle).
  • Whatever solution we choose, we are free to upgrade/downgrade/exit.

Cynapse is an Indian company and its managers are really great. For practical reasons, we decided to go with the managed service online. When Steven contacted them, asking for a rebate in exchange to us blogging about our experience with the tool and us feeding them with enhancement requests from the development point of view, they accepted and sustained our involvement.

Disclosure: Because the tool and the team is great, Milstein & associates is now a business partner and can resell and offer services on the top of the tool.

After one full year and two upgrades, we are very happy with the tool and its support. At one point, we have developed an offer for schools available at edu.cyn.in which has been extremely well adapted by the kids!

In addition to the collaboration aspect, Cynapse with its offering of various tools in one platform allows users to develop their online reputation in a controlled environment. As Craig Newmark, the founder of craigslist, mentions in a blog post:
People use social networking tools to figure out who they can trust and rely on for decision making. By the end of this decade, power and influence will shift largely to those people with the best reputations and trust networks, from people with money and nominal power. That is, peer networks will confer legitimacy on people emerging from the grassroots.
The Cynapse environment allows users to highlight their work: statistics about contributions and comments are shared on the main page, readers can note the published materials, etc. When people are new to social software, Cynapse offers a simple way to identify the people others “trust” and allows good contributors to build their reputation.

Aside our work on the !twetailer project, because the tool fits our needs, because kids adopted very well, we have proposed it to traditional companies (the ones that relies on Microsoft Outlook and shared folders for their collaboration). For now, the feedback is positive but it's too early to advance any adoption rate ;)

Why Open Platforms?

As a developer, I'm an heavy user of open source software (development environment, source control, build system, etc.). I am also a contributor myself with two open libraries hosted on github:
  • A set of utilities for Web application developers (Java, Python, JavaScript) which offers:
    • Globalization features: from one set of central repositories (TMX format) to programming language dependent resource bundles;
    • JSON related objects: to ease the parsing/generation of JSON structure
  • An adaptation of the Amazon Flexible Payment Service (FPS) library for the Java environment of Google App Engine.
And the content of this blog is offered under the Common Creative Licence By-NC-SA, which allows using beyond the traditional “fair-use” as long as you cite the source ;) I very like open platform for the reasons Larry Lessig gave in speech on . Open source is good for innovation for geeks like myself. For enterprises, I would argue that the key point is the data access: at anytime, someone can get the data out of an open source project without risking any patent infringement, without risky reverse-engineering. For sure, it won't be free as in “free beer” but they'll be free to get their data back. Some people will argue that close software often offer a way to export data in a standard format (like a SQL dump for a database) and allow then to import them somewhere else, but that's only true if no feature are dropped during the export and if all features from one can be activated in two during the import.

To summarize my point, I would say that open source software allow anyone to exit at anytime while continuing to control the data.

As a collaboration tool, Cynapse is maybe not the best one but it offers competitive advantages for the right price (for free if you have the team and expertise to manage it, for fee if you choose the hassle-free solution of the hosted service on Amazon AWS) while letting users migrating to another platform anytime.

Important point to consider during the selection of an open source solution: the quality of its community. More active is the community, better are your chances that issues you are facing have been documented, better your chances to see your (excellent) enhancement requests supported by others. Being able to contribute to an active community (by submitting bug reports, by answering others' questions, by submitting patches) have also the side benefit of improving your reputation.

A+, Dom

Sunday, March 21, 2010

Amazon FPS library for the Google App Engine environment

Here is a post on Amazon Flexible Payments System (FPS) and the beauty of the open source model!

Amazon FPS as the payment platform

!twetailer is an application connecting people to people, consumers to retailers and vice-versa. The business model is freemiun-based:
  • Posting demands, receiving proposals, and accepting proposals is available for free to any customers.
  • Listening to demands, proposing products or services, and closing deals is available for free to any registered retailers.
  • At one point, !twetailer is going to offer retailers to ask consumers to pay for the accepted products or services. The payment platform is going to be Amazon FPS1.

Amazon FPS is a really neat platform, the only one to my knowledge allowing to organize money transfers between third-parties. With Amazon FPS, !twetailer will be able to convey money from the consumers directly to the retailers, without the money transiting on !twetailer own account! This is a really safe mechanism.

As a quick introduction to Amazon FPS, I would strongly suggest you listen to that one hour webcast introduced on Amazon Payments blog on April 7, 2009: Monetize your innovation with Amazon FPS. If you use the open-source tool VideoLAN VLC, you can load the ASX file directly from Akamai from here.

Amazon and the open-source model

Amazon FPS, as many others Amazon Web Services (AWS), allows third-party applications to use its services through very simple APIs which are HTTP based! The libraries that developers need to use are mostly wrappers over HTTP connections with some specific controllers formatting the requests and signing them (to avoid a man-in-the-middle process tampering them).

Because HTTP is an open protocol and because Amazon could not probably develop its libraries for all possible Web servers, Amazon opened the libraries' source and attached to them a very liberal license2.

This is a very respectable attitude regarding their customers and also very well thought on the business-side: if developers can adopt their libraries for their own needs, Amazon won't have to pay for the corresponding development and it will enlarge the set of applications their platform can serve!

Amazon FPS on Google App Engine platform

The !twetailer server-side logic is Java based and dropping the Amazon FPS library freshly compiled in war/WEB-INF/lib is simple. However, the Amazon FPS code cannot run as-is because of few App Engine limitations...

The first one is encountered when the application needs to build the URL that will launch the co-branded service, a page that will allow consumers to pay for the service or product previously proposed by a retailer.
The static method HttpURLConnection.setFollowRedirects(boolean) controls the VM behavior and is then guarded by a JVM permission.
Read the incident report in the Google App Engine discussion group.

Fixing this issue is simple: tune the ability to follow redirection on the connection itself instead of applying the settings globally.

The second issue is really major:
The library uses the Jakarta Commons HttpClient component to convey payment requests from the application to the Amazon infrastructure. And many of its underlying calls are blocked in Google App Engine Java environment.
I asked for advices on AWS FPS forums. But without response, I have decided to go with my own wrapper of the Google URL Fetch mimicking the HttpClient HttpConnectionManager and HttpConnection classes.

Wrappers of Google URL Fetch for Amazon FPS

Following Amazon's leadership, I offer the URL Fetch wrappers that allows Amazon FPS to work on Google App Engine platform:
The code currently available works in the simple scenario !twetailer needs. But it is still under development. And the test suite covering it is not yet completed.

UrlFetchConnectionManager class definition
/******************************************************************************* 
 *  Adaptation for the Amazon FPS library to work on the Java platform of
 *  Google App Engine.
 *  
 *  Copyright 2010 Dom Derrien
 *  Licensed under the Apache License, Version 2.0
 */

package domderrien.wrapper.UrlFetch;

import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;

public class UrlFetchConnectionManager implements HttpConnectionManager {

 private HttpConnectionManagerParams params;
 private HttpConnection connection;
 
 public void closeIdleConnections(long timeout) {
  throw new RuntimeException("closeIdleConnections(long)");
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration) {
  throw new RuntimeException("getConnection(HostConfiguration)");
  // return null;
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) throws HttpException {
  throw new RuntimeException("getConnection(HostConfiguration, long)");
  // return null;
 }

 public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration, long timeout) throws ConnectionPoolTimeoutException {
  // As reported in http://code.google.com/appengine/docs/java/urlfetch/usingjavanet.html#Java_Net_Features_Not_Supported
  // > The app cannot set explicit connection timeouts for the request.
  if (connection != null) {
   releaseConnection(connection);
  }
  connection = new UrlFetchHttpConnection(hostConfiguration);
  return connection;
 }

 public HttpConnectionManagerParams getParams() {
  return params;
 }

 public void releaseConnection(HttpConnection connection) {
  connection.releaseConnection();
 }

 public void setParams(HttpConnectionManagerParams params) {
  // Parameters set in AmazonFPSClient#configureHttpClient:
        // - ConnectionTimeout: 50000 ms
  // - SoTimeout: 50000 ms
  // - StaleCheckingEnabled: true
  // - TcpNoDelay: true
  // - MaxTotalConnections: 100 (as proposed in the default config.properties file)
  // - MaxConnectionsPerHost: 100 (as proposed in the default config.properties file)

  this.params = params;
 }
 
}

UrlFetchConnection class definition
/******************************************************************************* 
 *  Adaptation for the Amazon FPS library to work on the Java platform of
 *  Google App Engine.
 *  
 *  Copyright 2010 Dom Derrien
 *  Licensed under the Apache License, Version 2.0
 */

package domderrien.wrapper.UrlFetch;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;

import javamocks.io.MockInputStream;
import javamocks.io.MockOutputStream;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class UrlFetchHttpConnection extends HttpConnection {

 private static URLFetchService urlFS = URLFetchServiceFactory.getURLFetchService();

 private HostConfiguration hostConfiguration;
 private HTTPRequest _request;
 private HTTPResponse _response;
 private MockOutputStream _requestBody = new MockOutputStream();
 private MockInputStream _responseBody = new MockInputStream();

 
 private HTTPRequest getRequest() throws MalformedURLException {
  if (_request == null) {
   _request = new HTTPRequest(
     new URL(hostConfiguration.getHostURL()), 
     HTTPMethod.POST, // AmazonFPSClient#invoke(Class, Map) uses only POST method
     FetchOptions.Builder.disallowTruncate().followRedirects()
   );
  }
  return _request;
 }
 
 private static final String SEPARATOR = ": ";
 private static final int SEPARATOR_LENGTH = SEPARATOR.length();
 private static final String NEW_LINE = "\r\n";

 private HTTPResponse getResponse() throws MalformedURLException, IOException {
  if (_response == null) {
   // Get the response from the remote service
   _response = urlFS.fetch(getRequest());
   // Rebuild stream of HTTP headers (except the HTTP status retrieved from readLine(String) method)
   StringBuilder buffer = new StringBuilder();
   for (HTTPHeader header: _response.getHeaders()) {
    buffer.append(header.getName()).append(SEPARATOR).append(header.getValue()).append(NEW_LINE);
   }
   buffer.append("Content-Length: ").append(_response.getContent().length).append(NEW_LINE);
   buffer.append(NEW_LINE);
   // Rebuild stream of HTTP content (chunked-encoded)
   buffer.append(Integer.toString(_response.getContent().length, 16)).append(";chunk size").append(NEW_LINE);
   buffer.append(new String(_response.getContent())).append(NEW_LINE);
   buffer.append("0;").append(NEW_LINE);
   _responseBody.resetActualContent(buffer.toString());
  }
  return _response;
 }

 /**
  * Default constructor
  * @param hostConfiguration
  */
 public UrlFetchHttpConnection(HostConfiguration hostConfiguration) {
  super(hostConfiguration);
  this.hostConfiguration = hostConfiguration;
 }

 @Override
 protected void assertNotOpen() throws IllegalStateException {
  throw new RuntimeException("assertNotOpen()");
 }

 @Override
 protected void assertOpen() throws IllegalStateException {
  assert(_response != null);
 }

 @Override
 public void close() {
  // Nothing to do!
 }

 @Override
 public boolean closeIfStale() throws IOException {
  // Safe call, passed to the inherited method
  return super.closeIfStale();
 }

 @Override
 protected void closeSocketAndStreams() {
  throw new RuntimeException("closeSocketAndStreams()");
 }

 @Override
 public void flushRequestOutputStream() throws IOException {
  getRequest().setPayload(_requestBody.getStream().toString().getBytes());
 }

 @Override
 public String getHost() {
  return hostConfiguration.getHost();
 }

 @Override
 public HttpConnectionManager getHttpConnectionManager() {
  throw new RuntimeException("getHttpConnectionManager()");
 }

 @Override
 public InputStream getLastResponseInputStream() {
  throw new RuntimeException("getLastResponseInputStream()");
 }

 @Override
 public InetAddress getLocalAddress() {
  throw new RuntimeException("getLocalAddress()");
 }

 @Override
 public HttpConnectionParams getParams() {
  return new HttpConnectionParams();
 }

 @Override
 public int getPort() {
  return hostConfiguration.getPort();
 }

 @Override
 public Protocol getProtocol() {
  return hostConfiguration.getProtocol();
 }

 @Override
 public String getProxyHost() {
  throw new RuntimeException("getProxyHost()");
 }

 @Override
 public int getProxyPort() {
  throw new RuntimeException("getProxyPort()");
 }

 @Override
 public OutputStream getRequestOutputStream() throws IOException, IllegalStateException {
  return _requestBody;
 }

 @Override
 public InputStream getResponseInputStream() throws IOException {
  return _responseBody;
 }

 @Override
 public int getSendBufferSize() throws SocketException {
  throw new RuntimeException("getSendBufferSize()");
 }

 @Override
 protected Socket getSocket() {
  throw new RuntimeException("getSocket()");
 }

 @Override
 public int getSoTimeout() throws SocketException {
  throw new RuntimeException("getSoTimeout()");
 }

 @Override
 public String getVirtualHost() {
  throw new RuntimeException("getVirtualHost()");
 }

 @Override
 protected boolean isLocked() {
  throw new RuntimeException("isLocked()");
 }

 @Override
 public boolean isOpen() {
  // Safe call, passed to inherited method
  return super.isOpen();
 }

 @Override
 public boolean isProxied() {
  // Safe call, passed to inherited method
  return super.isProxied();
 }

 @Override
 public boolean isResponseAvailable() throws IOException {
  return _response != null;
 }

 @Override
 public boolean isResponseAvailable(int timeout) throws IOException {
  return _response != null;
 }

 @Override
 public boolean isSecure() {
  return hostConfiguration.getPort() == 443;
 }

 @Override
 protected boolean isStale() throws IOException {
  throw new RuntimeException("isStale()");
 }

 @Override
 public boolean isStaleCheckingEnabled() {
  throw new RuntimeException("isStaleCheckingEnabled()");
 }

 @Override
 public boolean isTransparent() {
  // Safe call, passed to the inherited method
  return super.isTransparent();
 }
 
 @Override
 public void open() throws IOException {
  // Nothing to do
 }

 @Override
 public void print(String data, String charset) throws IOException, IllegalStateException {
  // Save the passed HTTP headers for the request
  int idx = data.indexOf(SEPARATOR);
  if (idx != -1) {
   String name = data.substring(0, idx);
   String value = data.substring(idx + SEPARATOR_LENGTH).trim();
   getRequest().addHeader(new HTTPHeader(name, value));
  }
  // Other information are just ignored safely 
 }

 @Override
 public void print(String data) throws IOException, IllegalStateException {
  throw new RuntimeException("print(string): " + data);
 }

 @Override
 public void printLine() throws IOException, IllegalStateException {
  throw new RuntimeException("printLine()");
 }

 @Override
 public void printLine(String data, String charset) throws IOException, IllegalStateException {
  throw new RuntimeException("printLine(string, String): " + data + " -- " + charset);
 }

 @Override
 public void printLine(String data) throws IOException, IllegalStateException {
  throw new RuntimeException("printLine(string): " + data);
 }

 @Override
 public String readLine() throws IOException, IllegalStateException {
  throw new RuntimeException("readLine()");
 }

 private boolean waitForHttpStatus = true;
 
 @Override
 public String readLine(String charset) throws IOException, IllegalStateException {
  if (waitForHttpStatus) {
   // Dom Derrien: called only once to get the HTTP status, other information being read from the response output stream
   int responseCode = getResponse().getResponseCode();
   String line = "HTTP/1.1 " + responseCode;
   switch(responseCode) {
    case HttpStatus.SC_OK: line += " OK"; break;
    case HttpStatus.SC_BAD_REQUEST: line += " BAD REQUEST"; break;
    case HttpStatus.SC_UNAUTHORIZED: line += " UNAUTHORIZED"; break;
    case HttpStatus.SC_FORBIDDEN: line += " FORBIDDEN"; break;
    case HttpStatus.SC_NOT_FOUND: line += " NOT FOUND"; break;
    case HttpStatus.SC_INTERNAL_SERVER_ERROR: line += " INTERNAL SERVER ERROR"; break;
    case HttpStatus.SC_SERVICE_UNAVAILABLE: line += " SERVICE UNAVAILABLE"; break;
    default: line = "HTTP/1.1 " + HttpStatus.SC_BAD_REQUEST + " BAD REQUEST";
   }
   waitForHttpStatus = false;
   return line;
  }
  throw new RuntimeException("readLine(String)");
 }

 @Override
 public void releaseConnection() {
  // Do nothing, connection closed automatically...
 }

 @Override
 public void setConnectionTimeout(int timeout) {
  throw new RuntimeException("setConnectionTimeout(int)");
 }

 @Override
 public void setHost(String host) throws IllegalStateException {
  throw new RuntimeException("setHost(String");
 }

 @Override
 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
  throw new RuntimeException("setHttpConnectionManager(HttpConnectionManager");
 }

 @Override
 public void setLastResponseInputStream(InputStream inStream) {
  // Safe call, passed to inherited method
  super.setLastResponseInputStream(inStream);
 }

 @Override
 public void setLocalAddress(InetAddress localAddress) {
  throw new RuntimeException("setLocalAddress(InetAddress)");
 }

 @Override
 protected void setLocked(boolean locked) {
  // Safe call, passed to inherited method
  super.setLocked(locked);
 }

 @Override
 public void setParams(HttpConnectionParams params) {
  throw new RuntimeException("setParams(HttpConnectionParams)");
 }

 @Override
 public void setPort(int port) throws IllegalStateException {
  throw new RuntimeException("setPort(int)");
 }

 @Override
 public void setProtocol(Protocol protocol) {
  throw new RuntimeException("setProtocol(Protocol)");
 }

 @Override
 public void setProxyHost(String host) throws IllegalStateException {
  throw new RuntimeException("setProxyHost(String)");
 }

 @Override
 public void setProxyPort(int port) throws IllegalStateException {
  throw new RuntimeException("setProxyPort(int)");
 }

 @Override
 public void setSendBufferSize(int sendBufferSize) throws SocketException {
  throw new RuntimeException("setSendBufferSize(int)");
 }

 @Override
 public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
  // Safe call, passed to inherited method
  super.setSocketTimeout(timeout);
 }

 @Override
 public void setSoTimeout(int timeout) throws SocketException, IllegalStateException {
  throw new RuntimeException("setSoTimeout(int)");
 }

 @Override
 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
  throw new RuntimeException("setStaleCheckingEnabled(boolean)");
 }

 @Override
 public void setVirtualHost(String host) throws IllegalStateException {
  throw new RuntimeException("setVirtualHost(String)");
 }

 @Override
 public void shutdownOutput() {
  throw new RuntimeException("shutdownOutput()");
 }

 @Override
 public void tunnelCreated() throws IllegalStateException, IOException {
  throw new RuntimeException("tunnelCreated()");
 }

 @Override
 public void write(byte[] data, int offset, int length) throws IOException, IllegalStateException {
  throw new RuntimeException("write(byte[], int, int): " + new String(data) + ", " + offset + ", " + length);
 }

 @Override
 public void write(byte[] data) throws IOException, IllegalStateException {
  throw new RuntimeException("write(byte[]): " + new String(data));
 }

 @Override
 public void writeLine() throws IOException, IllegalStateException {
  // Safe call, new line being inserted automatically by the HTTPRequest renderer
 }

 @Override
 public void writeLine(byte[] data) throws IOException, IllegalStateException {
  throw new RuntimeException("writeLine(byte[]): " + new String(data));
 }
}

Anyone is free to fork it for his own needs. Be careful with the code because I deliver it without warranties! If you have issues to report, if you can document how to reproduce them, depending on my workload, I will help you. If you fix the issue on your side, I will be happy to merge the corresponding patches into my main branch.

I hope this helps,
A+, Dom
--
Notes:
  1. At least in United States of America until Amazon extends its coverage to company without a US bank account.
  2. Apache License, Version 2.0, January 2004, which allows users to make modifications while keeping them private.

Thursday, March 11, 2010

Ladies and gentlemen, here is !twetailer

On Tuesday March 9 evening, my partner Steven Milstein and I attended a Montreal NewTech event. As three other companies, Steven pitched our project !twetailer to a crowd of 30-40 people. After few closed presentations, it was our first public pitch and it went very well! Read Steven's blog post for the details.

Background

I met Steven while working for IBM Rational. He was the Business Analyst while I was the Software Architect for the Web client of the Rational Portfolio Manager product. When Steven came to me with the Reverse Retailing idea, we agreed to develop it as a proof-of-concept for our own expertise.

We worked so well that we submitted our project to TechCrunch50 (TC50). If we were not among the 50 finalists, we passed the first selection round on 1000+ applicants and we had the chance to present it to Jason Calacanis during 15 minutes last August.

Boosted by the appreciation we got from Jason, we continued to focus on developing user stories and the corresponding code. We were ready to demo the full cycle early this January. Two months later, after many tests and fine tuning, we are opening !twetailer to broader audience!

Presentation

!twetailer first objective is to “connect supply and demand”, specifically “connect consumers to retailers” in its first version.

Look at this original presentation to get the sense of !twetailer ;) We made it months ago but it's still very accurate. Don't miss the part describing the hub, starting at 3:00.

After the presentation, we got many feedback from the audience and most of our interlocutors got it right. Here is the interpretation of Max Maheu, the presenter of the SolidWild company:
  • Max: If I register my company and listen for the tags “3d printing prototype logo”, all people using !twetailer will get to me?
  • Me: Exactly. All demands posted with one or many corresponding tags in the Montreal area will be forwarded to you, will be routed to you for free ;)
  • ...
  • Me: At this stage, it's possible you'll get unrealistic demands, many of them with the #demo tag. But if you respond to them, that means if you propose something, your message will be routed back to the consumers and your business information will be displayed to them!
  • Max: It's like advertising my business then! Cool.
What's next?

As mentioned by Guy Kawasaki in Montreal in March 2009, we have chosen to "launch early and to correct progressively". If all the delivered features are fully functional, there is still a long road to go in delivering the full feature set.

At this step, we need to get users, that means consumers and retailers, playing with the system and giving us their feedback.

To collect the information, we have organize the community site twetailer.cyn.in, implemented with the excellent social software from Cynapse. To value the contributions, we have setup a Work-for-attribution protocol: any community member that makes a significant contribution will have his/her work publicly recognized. Any volunteer?

We need also to work on the marketing side:
  • The brand !twetailer seems too tightly related to Twitter, while using Twitter is just one among the various set of connectors taking to our engine.
  • If !twetailer initial targets are consumers and retailers in a public market, it can work with closed markets, where wholesalers communicate with manufacturers, for example. This aspect needs to be documented and illustrated.
  • To ensure a vibrant life to !twetailer, we have plan to open its API to third-party developers, a bit a-la Twitter. Closed friends have already accepted to develop clients (under the Work-for-Attribution CLA) that will exercise it, but we need a stronger communication there too.
Thanks a lot to our families and closed friends for encouraging us on the entrepreneurship path. Thanks to anyone for the feedback because they help us improving !twetailer.

Call to contributors

!twetailer is a big project with a development still growing, so there's a lot of room for anyone to showcase their knowledge!
  • If you're a developer with Java/JavaScript skills,
  • If you're a tester with automation experience,
  • If you're a UI designer & Interactivity specialist,
  • If you're marketer with a Web 2.0 & Social software experience,
  • If you're a simple consumer in touch with a vibrant community,
  • If you're a business owner looking for new ways to reach your customers,
  • Etc.
Don't hesitate to contact Steven (Steven@Twitter or Steven@Twetailer.com) or myself (Dom@Twitter or Dom@Twetailer.com) and we'll exchange on our community site at twetailer.cyn.in.

A+, Dom

Monday, February 8, 2010

Ma nouvelle vie en tant qu'entrepreneur !

La démission

Il y a trois semaines, j'ai démissionné de mon poste de Consultant technique pour la compagnie Compuware, à Montréal. Plusieurs facteurs m'ont poussé à prendre cette décision :
  • Mon projet [toujours secret] personnel arrive à maturité et les conditions de sa mise en marché sont à envisager sérieusement;
  • Mon boss Paul Czarnik m'assigne sur un projet en relation avec l'intégration des outils de Gomez (récemment achetée par Compuware) dans le produit phare Vantage.
  • Le récent chaos provoqué par le tremblement de terre en Haïti offre une opportunité de proposer mon projet personnel aux organismes d'aide, dans la même veine de ce qui sera proposé aux organismes de Microfinance, comme Diku Dilenga et Jamii Bora.
D'un côté, il y avait un assignement plus prenant avec mon employeur, et de l'autre, il y avait une demande d'attention plus importante pour aborder une phase critique. J'ai finalement opté pour la démission. Youpi, je suis maintenant indépendant !

Certains amis m'ont demandé si cela avait un rapport avec mon âge (ils se souviennent de mon articule Le cap des 40 ans). Réponse simple: pas du tout, pas de middle-age crisis en vue ;)

C'est plus le cotoiement, même par Internet ou livre interposé, avec des gens inspirant comme Guy Kawasaki (GK's keynote in Montreal) qui me poussent à aller de l'avant, pour « faire quelque chose qui a du sens. »

Et maintenant ?

Du temps où je travaillais en tant qu'Architecte logiciel pour IBM Rational, à Montréal, j'avais eu le plaisir de travailler avec Steven Milstein qui avait le rôle d'Analyste d'affaires.

L'idée originale du projet qui m'occupe dorénavant est de Steven. Avec mon expérience, j'ai pris en charge le développement en nous appuyant sur des outils très accessibles comme :
Le travail de collaboration (user stories, blogues techniques, support à la communauté des participants, etc.) est colligé sur la plateforme cyn.in, sélectionnée par Steven pour sa grande qualité et l'évolution rapide de son développement.

Dans l'immédiat, je me concentre sur la mise au point de l'outil, passant d'un rythme de 15 à 20 heures par semaine à quelques 60 heures et plus. En ce moment, le code représente environ 13.000 lignes de code pour 33.000 lignes de test, rien que pour la partie serveur !

Si nous avons fait plusieurs présentations réelles du produit, nous sommes fin prêts pour faire des présentations plus larges, toujours sous le coup d'accord de non divulgation cependant. Idéalement, nous souhaitons avoir des partenariats pour avoir une masse critique et aller de l'avant en ouvrant les portes à tout à chacun ! Excitant, non ?

Parce que nous pensons que notre produit peut aider à la coordination des équipes d'aide aux sinistrés en Haïti, nous avons pris des contacts pour le leur proposer à titre gratuit, mais pour lequel il faut assumer les frais d'exploitation. Comme les contacts se font au niveau gouvernemental, du Canada et du Québec, ou au niveau de grandes ONGs comme la Croix Rouge ou MSF, il risque de se passer un certain temps avant que notre proposition trouve un écho favorable. Le suivi des contacts se poursuit.

Le futur

Pour le moment, je suis sans revenu. N'ayant pas de dettes colossales, ayant un train de vie modeste, je pense pouvoir être en roue libre pendant quelques mois sans que ma famille en patisse.

Avec Steven, en attendant que le grand projet avance, et en exploitant notre expérience des outils de socialisation, nous avons développé une offre reposant sur cyn.in et dirigée vers les écoles et l'initiation des élèves. Le projet s'appelle edu.cyn.in—voir le blog de Steven pour plus de détails.

Pendant la période de lancement, étant toujours « programmeur » sur des technologies récentes, j'ai le sentiment que mon capital de « connaissance technologique » restera solide. Ma communication sur ce blogue en témoignera ;) Le cas échéant, je pourrais toujours aller à la recherche de contrat de consultant pour partager mes connaissances.

Mon activité pour l'organisme de microfinance Diku Dilenga continue, toujours pour le soutien technique, moins en tant qu'investisseur privé. Si le grand projet décolle par contre, je consacrerai du temps pour son adaptation aux organismes de microfinance avec pour objectif d'aider les microentrepreneurs et de d'offrir une source de revenus aux dits organismes.

À suivre donc,
A+, Dom