Your Own Capabilities
Also known as: interface port adapter dependency injection mock fake external service plugin provider
● Intermediate5 minBefore this: Capabilities, InterfacesSpec 8.2
Declare a capability for an outside system your code depends on, such as payments or email. Code calls it by name; who answers is decided elsewhere.
capability Name {
fn method(param: Type) -> Result ! ErrorWithDenied
}type PayError = Declined | Denied(reason: String)
capability Payments {
fn refund(order: Int, amount: Int) ! PayError
fn charge(order: Int, amount: Int) ! PayError
}
type TestBank: Payments {
fn refund(self, order: Int, amount: Int) ! PayError {
print("refunded {amount} on order {order}")
}
fn charge(self, order: Int, amount: Int) ! PayError {
if amount > 1_000 {
fail PayError.Declined
}
print("charged {amount} on order {order}")
}
}
agent Shop {
can Payments
on buy(order: Int, amount: Int) ! PayError {
try Payments.charge(order, amount)
}
on cancel(order: Int, amount: Int) ! PayError {
try Payments.refund(order, amount)
}
}
fn main() {
const shop = Shop.start(Payments: TestBank())
shop.buy(1, 300) catch e => print("failed: {e}")
shop.buy(2, 5_000) catch e => print("failed: {e}")
shop.cancel(1, 300) catch e => print("failed: {e}")
}The program ran and printed:
kavach run own-implement.kav
charged 300 on order 1failed: Declinedrefunded 300 on order 1
capability Paymentslists what the outside system can do, like an interface.TestBankimplements it. A real one would talk to the bank over the network.- The agent
ShopcallsPayments.chargewithout knowing which implementation answers.
Tests hand in a fake implementation and production a real one, without changing the code that calls it. See Fakes for how a test hands one in.