The BSIL API is remote procedure call style (RPC), rather than RESTful. However, it's data model IS RESTful. So even though each call that operates on an Account requires a fully populated Account object, it will only do something with a small subset of the data you pass in. Unfortunately, it's not clear which portion. In order to fully create an Account, we make 6 calls to BSIL. What's more, the data returned is inconsistently populated. So we'll often have to re-load a value after updating it to get the complete new state of the object.
If BusiBee just mapped the various BSIL calls straight through, then all of this complexity would wind up littered throughout UCP. We wanted to be able to just update the values on an account, and tell BusiBee to update it, and trust that whatever needed to happen, would indeed happen.
Basically: Make the methods simple and consistent
There were a few redundant concepts, (eg: a Contact that is a CloudDirector as being separate from a User that is also the Cloud Director), and a few concepts we didn't need. (eg: UCP consumers don't care about applications on the User object.)
Basically: Make the data simple too.
BSIL has been under heavy development. Changes, both intentional and inadvertent happen. Many times, behavior changes because systems under it have changed as well.
We've written a comprehensive testing layer that verifies that BusiBee does what we expect it to do. This layer doesn't mock anything. It hits real BSIL and is run periodically by our CI server. It's alerted us to changes in: • responses • inputs • misconfiguration of environments • deteriorations of performance
Because we get feedback quickly from our CI server, we can usually narrow issues down to when a particular release was performed, which makes it easier to identify the cause. (And on our end, these specs are run every time we make a change to BusiBee at all) It's been invaluable in minimizing the time it takes to track down BSIL errors and regressions.
The slowness and frequent down time in the early goings of BSIL (or its underlying systems) frequently made it very difficult for us to know whether our code was fault or theirs was.
So we implemented our own version of BSIL that validates against the exact same feature specs that our real BSIL implementation runs against. When we implement a new feature in BSIL, we figure out how it works for real, and then we update our 'fake' layer (Called DBStore) to match it. This test system stores data in a local instance of redis.
Our UCP test suite generally runs against the FAKE version of BSIL, which is dramatically faster, and can be deterministically configured before each test run. It also allows us to continue development when real BSIL is down.
Note: DBStore isn't perfect. It mimics the behavior we expect at the boundaries of the BusiBee gem's services, not the individual calls to and from BSIL, which would have been prohibitively expensive. This does certainly bite us with regularity. (eg: If a user puts in a GUID for an id that is not valid, BSIL will blow up. But DBStore might not do the format validation, and accept the input as legitimate.) When that happens though, we try and insert validation into the gem itself to prevent the error in the future. So it's an iterative process. There are about ~2800 lines of specs (over 50% of the code base)
BusiBee's design and Nikola's reliance on standard protocols speaking standard JSON should make porting it straightforward.
Regardless of the approach, UCP will still use the BusiBee gem (that is where the clean model and service definitions live). However, we'd need to add a thin new 'NikolaStore'. That store would implement all of the same services as the other two, but the implementations would all look more or less like this:
# pseudocode
class AccountService
def create(account)
msg_id = queue.push('account/create', account.to_json)
queue.wait_for(msg_id) do |result|
account.attributes = AccountCoercer.coerce(result).attributes
end
end
end
The huge win here, is that the NikolaStore (and whatever code is on the other side of the queue that is powering it), can all be tested using the existing investment of spec code that is already testing DBStore and BSILStore.
As for what is on the other side of the queue, we have 2 options:
It should be straightforward to use BusiBee on the other side of this queue.
# pseudocode
queue.subscribe('account/create') do |message|
account = AccountCoercer.coerce(message)
BusiBee.AccountService.new.create(account)
queue.publish(account)
end
Writing the code in another language should be guided by the specs that are already available in ruby.
Further, if we'd like the service to ultimately be written in another language, but don't have time to do everything in a single milestone, we should be able to just write the UserService in another language. I'm assuming this would work by just letting each component register on the queue channels that it can service. Anything that could not be implemented could continue to rely on our BsilStore code path.
NOTE: There are a handful of caveats to this, especially with AccountService, because AccountService uses the UserService in some instances. There was nothing we could do about that, because the underlying BSIL create account calls will create both an account, and a user for that account. It should still be possible, but it might not necessarily be smooth.