Skip to content
Kavachकवच

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
}
syntax: Kavach code
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
own-implement.kav: Kavach code, then what running it prints
  • capability Payments lists what the outside system can do, like an interface.
  • TestBank implements it. A real one would talk to the bank over the network.
  • The agent Shop calls Payments.charge without 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.

One page for each form