The Daily Parker

Politics, Weather, Photography, and the Dog

Am I bringing my laptop to Korea?

Oh, you betcha:

On a year-over-year basis, average connection speeds grew by 25 percent. South Korea had an average speed of 14 Mbps while Japan came in second with 10.8 Mbps and the U.S. came in the eighth spot with 7.4 Mbps.

Year-over-year, global average peak connection speeds once again demonstrated significant improvement, rising 35 percent. Hong Kong came in first with peak speed of 57.5 Mbps while South Korea came in at 49.3 Mbps. The United States came in 13th at 31.5 Mbps.

Yes, South Korea has the fastest connectivity in the world. This I gotta see.

Plus, you know, clients.

US Airways and AMR cleared to merge

Saw this coming:

American Airlines and US Airways struck a settlement with the U.S. Justice Department that will allow the airlines to complete a $17 billion merger and create the world's largest carrier, the airlines announced Tuesday.

The deal, which heads off a trial planned later this month, calls for the combined airline to give up some takeoff-and-landing slots and some airport gates, including two American Airlines gates at Chicago O'Hare International Airport.

It also requires the combined airline to maintain Chicago and other airports as hubs for at least three years, something executives said they intended to do anyway and will keep long past three years.

Under terms of Tuesday's settlement, the airlines will give up 52 slot pairs at Washington Reagan National Airport and 17 slot pairs at New York LaGuardia Airport, as well as certain gates and related facilities to support service at those airports, the airline said. A slot pair entitles the holder to one departure and arrival.

EF6 "The default RetryManager has not been set" problem

Aw, buggre alle this for a Larke.

I'm all in favor of upgrades, but for Foucault's sake, don't break things. I'm trying to upgrade a .NET project to Entity Framework 6, and I want to smack the developers.

Under previous versions, you could set the retry manager through configuration. This was really helpful for unit testing, when you might want to change the configuration and have the application block load a transient fault handler automatically.

With Entity Framework 6 (EF6 — yes, this is blatant SEO), you have to set up the default transient fault handler in code:

[TestFixtureSetUp]
public void TestFixtureSetup()
{
	var strategy = new FixedInterval("fixed", 10, TimeSpan.FromSeconds(3));
	var strategies = new List { strategy };
	var manager = new RetryManager(strategies, "fixed");
	RetryManager.SetDefault(manager);
}

I mean, really? With EF6, you've got to put this code in every unit test sequence in your solution. Basically, that means you need to put it in every unit test file. Before, it had its own defaults.

Despite being all in favor of upgrades, I do get impatient when (a) the upgrade breaks existing code and (b) the entity performing the upgrade is one of the wealthiest entities in the history of business.

All right, then. Bring on the copy pasta...

The usability of HealthCare.gov

Jakob Nielsen's company has written a detailed analysis of how the Federal Health Exchange screwed up usability:

The HealthCare.gov team has suffered what most web professionals fear most: launching a broken web application. This is particularly harrowing given the visibility of the website in question. The serious technical and data issues have been covered extensively in the media, so we won’t rehash those. Instead, in this article we focus on how to improve the account setup process. This is a user experience issue, but fixing it will also alleviate the site's capacity problems.

Account Set-up Usability is Mission Critical

Account setup is users’ first taste of a service. A suboptimal account setup can spawn 3 problems:

  • Increased service cost: When people can’t self-service online and you have no competitors, they call you. Call-center interaction is more expensive than web self-service. In 2008, Forrester estimated call-center calls to cost $5.50 per call versus 10 cents for a user who self-services online.
  • Increased cognitive strain: The instructions for creating usernames and password in this flow (which we address further along in this article) require a great deal of concentration, and if users don’t understand the instructions, they will need to keep creating usernames and passwords until they are accepted.
  • Halo Effect: Account setup is the first in a series of web-based interactions that users will need to conduct on HealthCare.gov. A poor experience with this first step will impact how people feel not only about subsequent interactions with the site, but how they feel about the service in general and the Affordable Care Act as a whole.

The discussion around our office hinges on two things other than usability: first, give us $2 million (of the $400 million they actually spent) and we'll build a much better site. Second, the biggest problems come from the insurance companies on the back end. Users don't care about that; they just want to get health insurance. As Krugman says, though, there really wasn't a way to get the insurance companies out of the equation, and that, more than anything, is the foundation of all these other problems.

Lunchtime link list

Once again, here's a list of things I'm sending straight to Kindle (on my Android tablet) to read after work:

Back to work. All of you.

healthcare.gov is bad, but the Times should know better

I am agog at a bald impossibility in the New York Times' article today about the ACA exchange:

According to one specialist, the Web site contains about 500 million lines of software code. By comparison, a large bank’s computer system is typically about one-fifth that size.

There were three reporters in the byline, they have the entire Times infrastructure at their disposal, and still they have an unattributed "expert" opinion that the healthcare.gov codebase is 33 times larger than Linux. 500 MLOC? Why not just say "500 gazillion?" It's a total Dr. Evil moment.

Put in other terms: it's like someone describing a large construction project—a 20-story office building, say—as having 500 million rivets in it. A moment's thought would tell you that the mass of 500 million rivets would approach the steel output of South Korea for last month.

The second sentence is nonsense also. "A large bank's computer system?" Large banks have thousands of computer systems; which one did you mean? Back to my example: it's like comparing the 500-million-rivet office building to "a large bank's headquarters."

I wouldn't be so out of my head about this if it weren't the Times. But if they can't get this right, what hope does any non-technical person have of understanding the problem?

One last thing. We, the people of the United States, paid for this software. HHS needs to disclose the source code of this monster. Maybe if they open-sourced the thing, they could fix it faster.

Small world

The Chicago technology scene is tight. I just had a meeting with a guy I worked with from 2003-2004. Back then, we were both consultants on a project with a local financial services company. Today he's CTO of the company that bought it—so, really, the same company. Apparently, they're still using software I wrote back then, too.

I love when these things happen.

This guy was also witness to my biggest-ever screw-up. (By "biggest" I mean "costliest.") I won't go into details, except to say that whenever I write a SQL delete statement today, I do this first:

-- DELETE
SELECT *
FROM MissionCriticalDataWorthMillionsOfDollars
WHERE ID = 12345

That way, I get to see exactly what rows will be deleted before committing to the delete. Also, even if I accidentally hit <F5> before verifying the WHERE clause, all it will do is select more rows than I expect.

You can fill in the rest of the story on your own.

Is LinkedIn worth it?

I've never had much user for LinkedIn. Apparently I'm not alone:

The site’s initial appeal was as a sort of self-updating Rolodex—a way to keep track of ex-coworkers and friends-of-friends you met at networking happy hours. There’s the appearance of openness—you can “connect” with anyone!—but when users try to add a professional contact from whom they’re more than one degree removed, a warning pops up. “Connecting to someone on LinkedIn implies that you know them well,” the site chides, as though you’re a stalker in the making. It asks you to indicate how you know this person. Former coworker? Former classmate? Fine. “LinkedIn lets you invite colleagues, classmates, friends and business partners without entering their email addresses,” the site says. “However, recipients can indicate that they don’t know you. If they do, you’ll be asked to enter an email address with each future invitation.”

This frenetic networking-by-vague-association has bred a mordant skepticism among some users of the site. Scott Monty, head of social media for the Ford Motor Company, includes a disclaimer in the first line of his LinkedIn bio that, in any other context, would be a hilarious redundancy: “Note: I make connections only with people whom I have met.” It’s an Escher staircase masquerading as a career ladder.

I'll keep my LinkedIn profile active, of course, because I have to in my industry. But it's not like I'm hard to find online.

Managing multiple CRM connections with one Azure application

We've published Part 3 of my series of blog posts about integrating Holden International's Azure-based sales training app with multiple customer-relationship management (CRM) applications. The combined parts 1 and 2 went up mid-August. Part 4 should come out within 10 days.

Here's the complete post:


In my last blog post, I outlined the code architecture enabling us to let Holden International’s Azure-based efox 5.0 application connect with multiple, arbitrary CRM applications. In this post, I’ll show you what it looks like to the end user using our SalesForce connector.

Users whose accounts are integrated with SalesForce can enter efox in one of two ways: either by going directly to efox, or by clicking a “launch” button from a SalesForce Opportunity record.

Going through the front door is simple enough. The user can go directly to efox, or the user can navigate to a saved URL that takes advantage of efox’s RESTful ASP.NET MVC interface. In either case the user may not have already logged into SalesForce.

Some customers haven’t set up SSO with SalesForce yet, because the SalesForce Professional edition doesn’t support it. We’re trying to get everyone using efox to upgrade, because the user experience without SSO is sub-optimal: it’s DSO (double sign-on), requiring users to keep track of two different sets of credentials (one for efox, one for SalesForce). I’ll cover that mess next time.

For now, let’s look just at the SSO user experience.

Because efox has to support non-SSO users, just going to https://efox.holdenintl.com/ without being signed in to SalesForce takes you to the efox login page:

efox SignIn page

That’s not helpful to SSO users, because (a) they don’t have an efox login and (b) efox doesn’t know which SSO provider they’re using. To solve this problem, we use a RESTful URL that looks like https://efox.holdenintl.com/SSO/{CustomerCode}, where CustomerCode is unique tag identifying the customer.

Note how this URL lets the SSO controller not care what CRM system the customer is using. When the user navigates to their company’s efox SSO URL, efox looks up the customer and sends the user to the correct login page. So a SalesForce-integrated user gets passed to the SalesForce login page, with a SAML request under the hood telling SalesForce to send the user back to efox:

SalesForce login page

When the user logs in to SalesForce, the SAML magic happens, and they get back to their efox landing page:

Winning Sales Plan landing page

More commonly, though, users launch efox from their CRM systems. When we set up a customer to use efox, they add a “launch” button to their CRM’s Opportunity page:

SalesForce opportunity page with efox launch button

In SalesForce, the button is actually just a VisualForce link button; other systems just use a basic hyperlink. This is because it only needs to let users navigate to a link that follows the format https://efox.holdenintl.com/SSO/Import/{CRM Opportunity ID}/{User Name}. Navigating to that link takes the user to the SSOController class in efox, which starts the login process (if required) or sends the user to the next step (if she’s already logged in).

Again, through the miracle of MVC RESTful URLs, the link will work not only with SalesForce but with any other CRM provider. The SSO controller uses the user name to find out what customer to use, from which it finds out which SSO provider to use, to which it sends the user’s login request. The return path of the login request connects to the SalesPlanController class, which actually performs the import. (If the user is already logged in, the SSOController sends the user directly to the SalesPlanController.)

I won’t go over the specifics of how the controller pulls information from the CRM system, except to refer back to the discussion last time about the ICrmIntegration interface from last time.

I’ve used SalesForce for the artwork in this post, but the flow and user experience would be the same for any other CRM provider. Users get to move seamlessly between efox and their CRM system, and efox doesn’t care whether it’s connecting to SalesForce, Siebel, or a squirrel.

In my next post, I’ll talk about some of the problems we had to solve in order to let people use efox without SSO.

Integrating Multiple CRMs with One Azure Cloud Service

(This is cross-posted with the 10th Magnitude Tech Blog.)

Part 1: The Challenge

We developed efox, a Microsoft Windows Azure PaaS application, for our customer Holden International over the course of the last year. Our biggest challenge in the first release was to integrate their flagship sales training application, efox, with SalesForce. But from the beginning of the SalesForce integration effort, we had a constraint we couldn’t ignore: Not only would efox have to integrate with SalesForce, but it would also have to integrate with Oracle Siebel CRM, Microsoft Dynamics, and whatever other CRM application Holden’s customers want to throw at it.

At first glance it looks like a simple problem. Lots of applications, after all, integrate with CRMs. There are also lots of tools to help: SalesForce has a decent SDK, Microsoft has Windows Identity Foundation, most providers (including SalesForce) support OAuth...been there, coded that.

In a typical integration, the custom application extends the service provider’s application. For example, SalesForce has tools to create custom SalesForce extensions that extend SalesForce functionality. An application can run as a SalesForce user and share data with SalesForce transparently, using OAuth and the SalesForce REST API. This is, in fact, how the previous version of efox worked.

efox inverts the problem. Yes, it’s easy to integrate an application with one CRM; but it’s hard to integrate with all of them—simultaneously. At any moment, efox could have several hundred concurrent users going to half a dozen different CRM systems. The application therefore needs to treat each user’s session and each CRM request as a discrete unit.

In this and two succeeding posts, I’ll outline how we solved the architectural problem of separating the UI and back end to abstract away the specific CRM implementation from everything else; how we use ASP.NET MVC 4 routing to let people click a button in their CRM system to launch efox; and how we added single sign-on (SSO) using SAML to allow each user SSO access to efox—or not.

Part 2: The Architectural Solution

Before trying to implement an solution, we first designed the application to handle any arbitrary CRM integration we cared about.

First we created an interface, ICrmIntegration, that exposes these basic operations:

IEnumerable<Account> Accounts(Guid userId);
IEnumerable<Contact> Contacts(int accountId);
SalesPlan GetSalesPlan(string crmOpportunityId, Guid userId);
CrmLoginResult Login(string userName, string password, out string message);
IEnumerable<Opportunity> Opportunities(int accountId);
void Save(SalesPlan salesPlan);

The application uses Factory classes to retrieve objects from the database. We created new Integrated_Factory classes (like IntegratedSalesPlanFactory) that are aware of CRM integration classes and aware of the concrete Factories. Instead of calling the concrete factories, the UI now calls the Integrated factories, which decide whether to retrieve objects from the database or from the user’s ICrmIntegration.

This design completely abstracts the concrete CRM integrations away from the application’s UI. A controller class needing to retrieve a list of Account objects for the user calls IntegratedAccountFactory.Find(userId). That method calls the IntegrationFactory to get the user’s CRM integration (an ICrmIntegration implementation). If IntegrationFactory returns null, meaning the user isn’t integrated with a CRM system, then the IntegratedAccountFactory calls the regular AccountFactory instead. Otherwise it calls the Accounts method on the integration object to retrieve the list of accounts from the user’s CRM system.

Here’s the code in IntegratedAccountFactory:

public static IEnumerable<Account> Find(Guid userId)
{
	var tenantId = AccountFactory.TenantId(userId);
	if (!tenantId.HasValue) return AccountFactory.All(userId);

	var integration = IntegrationFactory.Instance.Find(tenantId.Value, userId);

	return (null == integration) ?
		AccountFactory.All(userId) :
		integration.Accounts(userId);
}

The UI neither knows nor cares whether the particular user gets her accounts from efox, from SalesForce, or from a squirrel; all it cares about is getting a list of accounts. In a sense, this is a classic bridge pattern.

I'll have a couple more posts on efox's CRM integration over the next few weeks. Next up, I'll talk about how CRM integration looks to the end user, then after that, about how we integrated single sign-on (SSO) with multiple identity providers. Check back in a week or so for Part 3.