Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var BadReqTypeError = errors.New("bad request type")
BadReqTypeError should be returned by the Service.Handle method when it receives a req object of type it can't handle.
var GeneratedReqIDBytesLen = 16
GeneratedReqIDBytesLen is the number of random bytes included in newly generated RequestIDs returned by the default implementation of NewReqID. Note that NewReqID returns a string that is the hex representation of the random generated ReqID byte array so the length of the returned ReqID string is the double of this value.
var NewClient = func(svc Service, ownerName string) Client {
return &client{
svc: svc,
ownerName: ownerName,
}
}
NewClient creates a new Client object that can be used to send requests to the svc service. The ownerName can be anything but it should be the name of the service if the Client is created for a service.
The returned Client belongs to the owner specified by ownerName and requests made through the Client object will send the ownerName to the called services which can inspect ownerName in their request context in Ctx.ClientName.
You won't need this in your services/servers/tests. This function is public only to make nano hackable for experiments. You can replace this function with your own implementation to change the Client implementation returned by ClientSet.
var NewClientSet = func(ss ServiceSet, ownerName string) ClientSet {
return &clientSet{
ss: ss,
ownerName: ownerName,
}
}
NewClientSet creates a ClientSet object from the given ServiceSet for the owner identified by ownerName. The ownerName can be anything but it should be the name of the service if the ClientSet is created for a service.
The returned ClientSet belongs to the owner specified by ownerName and requests made through the Client objects returned by this ClientSet will send the ownerName to the called services which can inspect ownerName in their request context in Ctx.ClientName.
You won't need this in your services/servers/tests. This function is public only to make nano hackable for experiments. You can replace this function with your own implementation to change the ClientSet implementation passed by NewServiceSet to ServiceInit.Init.
var NewContext = func(clientContext context.Context) (context.Context, context.CancelFunc) { c := context.Background() if clientContext == nil { return context.WithCancel(c) } var cancel context.CancelFunc if deadline, ok := clientContext.Deadline(); ok { c, cancel = context.WithDeadline(c, deadline) } else { c, cancel = context.WithCancel(c) } cancelChan := make(chan struct{}) go func() { select { case <-clientContext.Done(): case <-cancelChan: } cancel() }() return c, func() { close(cancelChan) } }
NewContext has to return a new context.Context object and a related cancel func for a new request. The returned cancel func is guaranteed to be called exactly once, it doesn't have to handle multiple calls like the cancel funcs returned by the standard context package.
The clientContext parameter might be nil if the initiator of the request isn't a service (e.g.: test) or if there is a transport layer between the caller and the called service. If both services reside in the same server executable then clientContext isn't nil but even in this case it is a bad idea to make clientContext the parent of the newly created context because that behavior would be very different from the scenario in which there is a transport layer between the services. However it completely makes sense to propagate the deadline and the cancellation of clientContext to the newly created context.
You won't need this in your services/servers/tests. This function is public only to make nano hackable for experiments.
var NewReqID = func() (string, error) { reqIDBytes := make([]byte, GeneratedReqIDBytesLen) _, err := rand.Read(reqIDBytes) if err != nil { return "", fmt.Errorf("error generating request ID :: %v", err) } return fmt.Sprintf("%x", reqIDBytes), nil }
NewReqID generates a new random request ID. The default implementation generates a random byte array of length defined by GeneratedReqIDBytesLen and converts the byte array into a hex string.
You won't need this in your services/servers/tests. This function is public only to make nano hackable for experiments.
var NewServiceSet = func(services ...Service) ServiceSet { ss := make(serviceSet, len(services)) for _, svc := range services { ss[svc.Name()] = svc } for _, svc := range services { if serviceInit, ok := svc.(ServiceInit); ok { err := serviceInit.Init(NewClientSet(ss, svc.Name())) if err != nil { panic(fmt.Sprintf("error initialising service %q :: %v", svc.Name(), err)) } } } for _, svc := range services { if serviceInitFinished, ok := svc.(ServiceInitFinished); ok { err := serviceInitFinished.InitFinished() if err != nil { panic(fmt.Sprintf("InitFinished error in service %q :: %v", svc.Name(), err)) } } } return ss }
NewServiceSet creates a new ServiceSet object from the given services. The creation of the ServiceSet involves initialising the services before this function returns. The initialisation of the services includes the resolution of dependencies between each other by obtaining Client interfaces to each other in their Init methods.
var NewTestClientSet = func(services ...Service) ClientSet { return NewClientSet(NewServiceSet(services...), "test") }
NewTestClientSet is a convenience helper for tests to wrap a set of services into a ServiceSet and then into a ClientSet in one step.
var RunServer = func(ss ServiceSet, listeners ...Listener) { runServer(func(l Listener, err error) { log.Println("Listener failure :: " + err.Error()) os.Exit(1) }, ss, listeners...) }
RunServer takes a set of initialised services and a list of listeners and initialises the listeners and then listens with them. Blocks and returns only when all listeners returned.
It panics if the Init of a listener returns an error. Terminates the server process if the Listen method of a listener returns with an error.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client interface { // Requests sends a request to the service targeted by this client object. // The c parameter is the context of the caller service but it is allowed // to be nil or a Ctx instance with only some of its fields set. // // Note the similarity between the signature of Client.Request and // Service.Handle. The reason for calling another service through a Client // interface instead of directly calling its Service.Handle method is that // the request context (the c parameter) isn't directly passed between // services. While all other parameters and return values are transferred // directly between Client.Request and Service.Handle a new request context // (*Ctx) has to be created when the control is transferred to another // service. While it might make sense to directly copy and pass some fields // of the request context of the caller (e.g.: Ctx.ReqID), most other fields // of the request context have to be adjusted (e.g.: Ctx.Svc) that is done // by this Client interface. // // To clarify things: The c parameter of Client.Request is the request // context of the caller, while the c parameter of the Service.Handler will // be another request context newly created for the called service. Request(c *Ctx, req interface{}) (resp interface{}, err error) }
Client is an interface that can be used to send requests to a service.
type ClientSet ¶
type ClientSet interface { // LookupClient returns a client interface to the service identified by the // given svcName. Trying to lookup a service that doesn't exist results in // a panic. This is by design to make the initialisation code simpler. // Normally services lookup their client at server startup in their Init // methods where a panic is acceptable. LookupClient(svcName string) Client }
ClientSet can be used by a service to obtain client interfaces to other services.
type Ctx ¶
type Ctx struct { // ReqID is the ID of the request that entered the cluster. Note that by // making calls to other services by calling Client.Request and passing this // context info the request inside the other service will copy this ReqID. // For this reason ReqID works as a correlation ID between the requests // handled by a cluster of your services and can be used for example to // track logs. ReqID string // Context can be useful when you call some std library functions that // accept a context. Context context.Context // Svc is the current service. Svc Service // ClientName is the name of the entity that initiated the request. It is // usually the name of another service but it can be anything else, for // example "test" if the request has been initiated by a test case. ClientName string }
Ctx holds context data for a given request being served by a service. If you fork and modify this repo (or roll your own stuff) for a project then one of the benefits is being able to specify the contents of your request context structure that is being passed around in your service functions. I've included a few sensible fields but your project might want to exclude some of these or perhaps add new ones. An example: If you implement authentication and authorization at the transport layer then you can use a field in the Ctx structure to pass user id info to the business logic if it makes sense in the given project.
type Listener ¶
type Listener interface { // Init is called with the initialised services of the server. Init(ServiceSet) error // Listen is called after Init. This method is called on its own goroutine, // this is how a server can listen on more than one listeners in parallel. Listen() error }
Listener is a component in the server executable that can receive requests from the outside world and forward them to the already initialised services located inside the server executable.
type Service ¶
type Service interface { // Name returns the name of the service. I recommend using simple names with // safe characters, snake_case is a very good choice. If you use simple // names then it will be easy to use the same name for directories, packages // host names, identifiers in code, etc... Name() string // Handle is the handler function of the service. It can receive any type // of request object and respond with any type of response. // If the type of the req parameter can't be handled by Handler then it // it should return a nano.BadReqTypeError. // Note that nil is a valid response value if you define the API of your // service that way. // // While there are no restrictions on the type of request and response // objects it is wise to use easy-to-serialize types (e.g.: json/protobuf // serializable types) in order to make it easy to transfer req/resp objects // through the wire. I recommend using struct pointers. Handle(c *Ctx, req interface{}) (resp interface{}, err error) }
Service is an interface that has to be implemented by all services.
type ServiceInit ¶
type ServiceInit interface { // Init is called when the ServiceSet is being created. This is the right // time to resolve the dependencies of this service by asking for the // clients of other services from the received ClientSet. It is discouraged // to store the received ClientSet for later use - use it only before // returning from Init. // // Note that when your Init is called other services might be uninitialised // so you shouldn't send requests to them through the clients your obtained // using the ClientSet parameter. If you have to send a request to // another service at init time then you should implement the // ServiceInitFinished interface. // // Init is guaranteed to be called before the InitFinished and Handler // methods of any service in the ServiceSet that is being created. Init(ClientSet) error }
ServiceInit is an interface that can optionally be implemented by a Service object. If a service implements this interface then it receives an Init call when the ServiceSet that contains this service is being created.
type ServiceInitFinished ¶
type ServiceInitFinished interface { // InitFinished is called during the creation of a ServiceSet only after // calling the Init of all services in the ServiceSet. // // InitFinished is guaranteed to be called after the Init of your service // but it isn't guaranteed to be called before the Handler method because // the InitFinished of other services might be called earlier than your // InitFinished and they might send requests to your service. InitFinished() error }
ServiceInitFinished is an interface that can optionally be implemented by a Service object. If a service implements this interface then it receives an InitFinished call when the ServiceSet that contains this service is being created.
type ServiceSet ¶
type ServiceSet interface { // LookupService returns the service with the specified name. LookupService(svcName string) (Service, error) }
ServiceSet is a set of initialised services. During initialisation the services have already resolved the dependencies between each other.
Directories ¶
Path | Synopsis |
---|---|
addons
|
|
discovery
Package discovery provides different ways to resolve the name of your services to their respective "addr:port".
|
Package discovery provides different ways to resolve the name of your services to their respective "addr:port". |
transport/http/serialization/gogo_proto
Package gogo_proto is a generated protocol buffer package.
|
Package gogo_proto is a generated protocol buffer package. |
examples
|
|
example1/api_go/svc1
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config.
|
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config. |
example1/api_go/svc2
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config.
|
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config. |
example1/api_go/svc3
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config.
|
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config. |
example1/api_go/svc4
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config.
|
DO NOT EDIT! This file has been generated from JSON by gen_http_transport_config. |