-
Notifications
You must be signed in to change notification settings - Fork 0
API best practices
A facade is a collection of API methods organised for a specific purpose. It's a pretty fundamental concept: our versioning is at facade granularity, and a facade's most fundamental responsibility is to validate incoming API calls and ensure that they're sensible before taking action.
Any type can be a facade; but if it doesn't have exported methods with suitable signatures, it won't be very interesting. Specifically, any exported method which (1) takes either 0 or 1 args; and (2) returns either (result) or (result, error): will be exposed over the websocket rpc connection.
When an api request is received, the apiserver chooses a facade constructor, based on the request's rootName and version; then constructs the facade and calls the method corresponding to the request's methodName. (Many details elided.) Note that the facade is created for the use of, and persists only for the lifetime of, a single request.
If any of the following conditions applies:
- you're writing a new worker inside an agent
- you're changing some existing worker, and it needs new capabilities
- you're exposing some new domain of functionality to an external user
- you're changing or extending some such exposed functionality
...you need to be writing and registering a new Facade. If it's a new worker or a new domain, it should definitely be in a new subpackage of apiserver
; if it's a change to an existing one it still needs a new facade -- to be registered under a new version -- but it should go in the same package as its predecessors.
-
don't import state
-
really, please, don't import state
-
define or locate interfaces that expose the capabilities your facade requires
- these might well be or want to be defined under
core
, if they're semi-mature - if it's a locally defined interface that's fine too
- if it's defined somewhere else in the codebase, be suspicious: probably, either you're depending on unnecessary concretions, or that interface is defined in the wrong place.
- these might well be or want to be defined under
-
write a simple constructor for your facade, most likely using the config-struct pattern, and making sure to include an
apiserver/common.Authorizer
:package whatever // Backend exposes capabilities required by the whatever facade. // // It's an example of a locally defined interface that's fine. // // Like all good interfaces, it should be tightly focused on its // actual purpose and prefer not to include methods that won't be // used. type Backend interface { DoSomething(to Something) error // more..? } // Config holds the dependencies and parameters needed for a // whatever Facade. // // Keep in mind that this is *completely specific to a class of // client*: the internal `whatever` facade is intended for the // exclusive use of the `whatever` worker; and all external facades // are for the exclusive use of, uh, external users. // // That is to say: there probably won't be much to configure apart // from the direct dependencies. Anything else is part of the // fundamental character of the facade in play, and should probably // not be tweakable at all. type Config struct { // Authorizer represents the authenticated entity that you're // responsible for granting or restricting access to. Possibly // "Authorisee" would have been a better name? Wouldn't object // if someone fixed it, I think it just came from the time when // Fooer was the default name for everything. Authorizer common.Authorizer // Backend exposes something-doing capabilities blah blah. Backend Backend // more..? many facades will want to access several distinct // backendy capabilities, consider carefully the granularity // at which you expose them. It's not a bad thing to pass in // the same object in several different fields, for example. } // Validate returns an error if any part of the Config is unfit for use. func (config Config) Validate() error { // This bit is significant! This is the moment at which you get to // choose who can use the facade at all. So a user-facing facade // would want to AuthClient(), and ErrPerm if false; an environment // management job would want to AuthEnvironManager(); or perhaps this // is just for some worker that runs on all machine agents, in which // case we do: if !config.Auth.AuthMachineAgent() { return common.ErrPerm } // ...and now, bingo, you know your methods will not be called without // the certainty that you're dealing with an authenticated machine agent. // Yay! // May as well check these too, it's better than panicking if someone // has messed up and passed us garbage. if config.Backend == nil { return errors.NotValidf("nil Backend") } return nil } // NewFacade creates a Facade according to the supplied configuration, or // returns an error. Note that it returns a concrete exported type, which // is right and proper for a constructor. func NewFacade(config Config) (*Facade, error) { if err := config.Validate(); err != nil { return nil, errors.Trace(err) } return Facade{config}, nil } // Facade exposes methods for the use of the whatever worker. It lives only // for the duration of a single method call, and should usually be stateless // in itself. type Facade struct { config Config } // ...and now define your methods.
note this dependency on mutable global state is bad. If someone were to fix it I would be most grateful.
- in your facade package
Testing
Releases
Documentation
Development
- READ BEFORE CODING
- Blocking bugs process
- Bug fixes and patching
- Contributing
- Code Review Checklists
- Creating New Repos
-
MongoDB and Consistency
- [mgo/txn Example] (https://github.com/juju/juju/wiki/mgo-txn-example)
- Scripts
- Update Launchpad Dependency
- Writing workers
- Reviewboard Tips
Debugging and QA
- Debugging Juju
- [Faster LXD] (https://github.com/juju/juju/wiki/Faster-LXD)