Arguments and results

After returning from API Days in London, I’m studying design patterns for APIs. The last paper I read was “Arguments and Results” by James Noble (1), published in 1997 and still extremely relevant. Part of my current work, developing a data warehouse, is to optimize the transport of huge volumes of data. And if I want to use APIs, how can I do this efficiently? There is a consensus that REST API’s are not suitable in high-volume scenarios and studying this topic I arrived at Noble.

The article discusses patterns for object protocols and for this there are two groups:

  • How to send objects (Arguments): Arguments object, Selector object and Curried object
  • How to receive objects (Results): Result object, Future object and Lazy object

Patterns

  • Arguments object: This pattern is utilised to streamline a method’s signature by consolidating common arguments and objects, and modifying the method signature to accept this consolidated object instead. It brings several advantages, you make your domain explicit; your customers can use what they have at hand (the objects, instead of separated values); and you are able to separate the processing logic from your business logic

From

    void Send-FixedIncome(EntityName, IssueDate, MaturityDate, PrincipalAmount, ...) { }

To

    void Send-FixedIncome(FixedIncome) { }

“adding an eleventh argument to a message with ten arguments is qualitatively quite different to adding a second argument to a unary message”

  • Selector object: When you have methods with quite similar arguments, this patterns introduces a selector that enables you to pick one of them. Noble mentioned you could a enum, maybe using GoF a Flyweight would do the trick.

From

    void CalculateHomeLoanAmount(HomeLoan, Collateral) { }
    void CalculateHomeLoanAmount(HomeLoan) { }
    void CalculateAutoLoanAmount(AutoLoan) { }
    void CalculatePersonalLoanAmount(PersonalLoan) { }

To

    void CalculateAmount(LoanType, Loan) { } //Loan would be the interface for all loan types

“Protocols (APIs) where many messages perform similar functions are often difficult to learn and to use, especially as the similarity is often not obvious from the protocol’s documentation”

  • Curried object: from the functional programming world, currying breaks a function with several arguments into smaller functions, where each one of those functions takes some (usually one) of the original arguments; these functions are called in sequence then. Noble introduces this pattern for a scenario where the caller should not need to supply all arguments (e.g. constants), the method can supply on their behalf. In GoF terms, it acts as a proxy.

From

    void SettleDebt(Loan, ParcelsToPay) { } # no. of parcels can be retrieved from the loan 

To

    int GetOpenParcels(Loan) { }
    void SettleDebt(Loan) { () => Settle(Loan) } # in Settle no. of parcels is retrieved

“These kinds of arguments increase the complexity of a protocol. The protocol will be difficult to learn, as programmers must work out which arguments must be changed, and which must remain constant.”

  • Results object: For complex scenatios, one might need several method calls to generate the expected result. Your results object should be a single one with everything on it. This pattern can be seen as the flip side of the Curried Object pattern. One aspect worth mentioning is when those several calls mean several systems. In this case. a result object helps to reduce the coupling and acts as an abstraction around them.

“Perhaps the computation returns more than on object”

  • Future object: This pattern is employed when we need to execute a time-consuming operation and perform other tasks while waiting for the operation to complete. In essence, we aim to process a result asynchronously and then likely invoke a callback upon completion.

” Sometimes you need to ask a question, then do something else while waiting for the answer to arrive”

  • Lazy object: Sometimes you need to supply a result, though it is not 100% sure whether the method will be called. The advantage of this is that we don’t get data unnecessarily.

“Some computations can best be performed immediately but the computation’s result may never be needed”

All in all, these concepts are quite widespread. Any mainstream programming language has support for async methods (Future object) and lazy evaluation (Lazy object). Moreover, good OO design makes ‘Arguments object’ and ‘Seletor object’ quite naturally appear. Even though it was a good back-to-basics article to remind me the importance of good design in my API methods.

(1) https://www.researchgate.net/publication/2733549_Arguments_and_Results