Background
One of my customers is a B2B connectivity company. It sells
software that enables companies to send and receive their electronic
business documents (purchase orders in EDI, etc.) over the internet
to their trading partners. Internet communications are much cheaper
than the legacy mode of EDI exchange, the VAN (Value Added Networks).
VANs are private networks set up by large companies that provide electronic
mailboxes for their customers and take care of routing and sending
the EDI documents. Connectivity to the VAN is generally via modem only.
For my customer, it was critical to its success to support not only
Internet communications, but also support VAN connectivity so that
customers could replace their entire system with my customer's software.
This new software would send documents over the Internet when possible,
or fall back to dialup via VAN for trading partners when required.
The customer had some VAN connectivity in its product, but it was built
on TAPI and ran on NT only, and it did not support popular file-transfer
protocols such as ZModem. The customer wanted to have its product run
on the major UNIX platforms and wanted more diverse file-transfer protocols.
Within a few weeks, I wrote a completely new VAN connectivity
subsystem that fitted seamlessly into their product. Since the release
of the new version of the customer's project that included my code,
zero defects were found and the number of customers calling in with
problems decreased dramatically. The customer considered my work to
be an overwhelming success.
Technical Aspects
The library had several modules that I tried to abstract into
higher-level C++ objects, and the cross-platform requirements
(UNIX and NT) made this a bit more difficult.
- Low-level Serial and Network I/O
-
I chose to use fully asynchronous I/O on both UNIX and NT platforms
because this would give a much more responsive system: the program would
not ever be completely blocked on a read or write operation. The library
supported both serial and TCP/IP connections, and the either kind of
IOChannel object could be passed to the worker objects transparently.
-
Modem dialers
-
In order to call a remote VAN over a serial line, the software first had to
dial them via the modem. This involved much more than just "dialing the phone"
(which is fairly standardized via the Hayes command set): I also had to
configure the modem properly. This mainly included parameters to set the
modulation and compression methods to be used (V.32? LAP/M?), plus
managing of flow control in the modem.
-
The modem objects were abstract, and adding new supported modems was
straightforward and centralized.
-
VAN Scripting
-
Once I'd connected with the target VAN - I supported a half dozen
of them - the software typically faced with a menu system that was designed
for a human operator, not a remote program. So I studied the menu systems
and created custom scripting code that knew how to nagivate these menus,
and it detected every error that I was able to simulate on my own.
-
I put the VAN scripting in a separate library to make adding a new one
straightforward, and even within each VAN module, I had separate "login", "logout",
and "transfer" methods. Each VAN had a descriptor object that informed
the caller about the VAN's full name, which I/O methods were supported,
the types of file transfers permitted, and the form of the login credentials
required to gain access to the system.
-
File Transfer Modules
-
Once connected and logged in, and after navigating the necessary
menus, I had to actually transfer the data. This was done with a separate
library of file-transfer modules, and I supported "raw" ASCII transfers,
XModem (of a few flavors), and ZModem. These transfer objects had just a
few entry points but plenty of status callback methods to allow the caller
to know how the transfer was doing.
-
Test Scaffolding
-
Initially, it was not practical to test this library in the customer's
server because of the long cycle time to build and install the server,
and to submit a job for processing. Taking ten minutes for each test run
would have made incremental debugging very tedious, so I instead created
a test skeleton that would exercise the library as the customer's server would.
-
Working with the customer's software architect, we together defined a single
"worker" object that would be the sole interface between their software
and mine. It had a handful of high-level methods and a few status
callbacks that would allow them to present a "transfer job" to this
object, and it would handle everything. For months, both I and the QA
department would use the tester tool to validate the various aspects
of the system: do we dial all the modems correctly? do we navigate
the VAN menus correctly? do the files transfer correctly? and so on.
This provided dramatically lower cycle times for testing.
When, near the end of the process, I bolted my library into the customer's
server, it compiled and ran correctly the first time. This was a
testament to the good design of the interface objects, and neither the
server engineer nor I really knew each other's code at all.