You're adding in a layer of abstraction that is confusing
Your API starts off very clean and simple. A HTTP POST creates a new Deposit resource with the given parameters. Then you go off the rails by introducing the idea of "actions" that are an implementation detail rather than a core part of the API.
As an alternative consider this HTTP conversation...
POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...
201 CREATED
Location=/card/123/account/456/Deposit/789
Now you want to undo this operation (technically this should not be allowed in a balanced accounting system but what the hey):
DELETE /card/123/account/456/Deposit/789
204 NO CONTENT
The API consumer knows that they are dealing with a Deposit resource and is able to determine what operations are permitted on it (usually through OPTIONS in HTTP).
Although the implementation of the delete operation is conducted through "actions" today there is no guarantee that when you migrate this system from, say, C# to Haskell and maintain the front end that the secondary concept of an "action" would continue to add value, whereas the primary concept of Deposit certainly does.
Edit to cover an alternative to DELETE and Deposit
In order to avoid a delete operation, but still effectively remove the Deposit you should do the following (using a generic Transaction to allow for Deposit and Withdrawal):
POST /card/{card-id}/account/{account-id}/Transaction
Amount=-100, different parameters...
201 CREATED
Location=/card/123/account/456/Transation/790
A new Transaction resource is created which has exactly the opposite amount (-100). This has the effect of balancing the account back to 0, negating the original Transaction.
You might consider creating a "utility" endpoint like
POST /card/{card-id}/account/{account-id}/Transaction/789/Undo <- BAD!
to get the same effect. However, this breaks the semantics of a URI as being an identifier by introducing a verb. You are better off sticking to nouns in identifiers and keeping operations constrained to the HTTP verbs. That way you can easily create a permalink from the identifier and use it for GETs and so on.
You need a data transfer protocol. It can be as simple as a string with comma-separated values, or as complex as some XML backed up by a schema. Whatever you decide, the receiver will have to be smart enough to decode it.
As an example, one of your commands could retrieve a comma-separated header:
TIME, ALTITUDE, TEMPERATURE, AIRSPEED, WHATEVER
This tells you not only the name of each signal, but how many signals there are. You can use this information to set up your decoder on the receiver side.
You could also use something like Protocol Buffers.
Best Answer
Yes it's absolutely OK to add parameters to the
execute()
method. It's good to remember how and why the command pattern is used: as a kind of lambda function. In Java 8, it is often no longer necessary to implement a concrete command class because we can use lambdas or method references instead, assuming thatICommand
is a@FunctionalInterface
.A command object may save some context in instance fields and a lambda may capture enclosing variables, but the execute method can still take extra parameters and even return values! Just take care to define the Command interface in a way that solves your problems. This could even mean that the Command interface requires multiple methods, in which case it can no longer be a functional interface.
As the traditional example for the command pattern. we might have a GUI where the user clicks on some button. We give the button a Command to execute. The button doesn't supply any data and can't use any return value, so we might define
In a completely different scenario, we might have an event driven account balance system. A command will receive the old account state and return the new account state. Then our design might look like:
It may of course be desirable to add further metadata to these commands (such as the transaction counterparty or the authorization for this command). The account could also keep a log of the applied commands, undo commands, and so on. So the command pattern can be more flexible than a simple lambda.