doc/tutorial-clock.md
# How to create a new service
This guide will show you how to create and test a new service.
In this tutorial, we will implements a timestamp service whose role is
to allow clients to produce synchronized timestamps. It is based on
the [clock service](https://github.com/lugu/qiloop/blob/master/examples/clock)
example.
## Define the service interface
The public interface of a service is described by an IDL file.
Create a file clock.qi.idl with the content:
package clock
interface Timestamp
fn nanoseconds() -> int64
end
This IDL file will be processed to generate the serializing code.
## Generate the server stub
The IDL file is used to generate the necessary boiler code to
implement the service. Use `qiloop` to generate the server stub:
qiloop stub --idl clock.qi.idl --output clock_stub_gen.go
## Automate the stub generation
In order to easily update the clock_stub_gen.go file, create a file
called generate.go with the following content:
//go:generate qiloop stub --idl clock.qi.idl --output clock_stub_gen.go
package clock
Then execute the command:
go generate
## Implement the service
The file clock_stub_gen.go defines an interface called
TimestampImplementor which describes the methods to be implemented in
order to create the timestamp service:
type TimestampImplementor interface {
Activate(activation bus.Activation, helper TimestampSignalHelper) error
OnTerminate()
Nanoseconds() (int64, error)
}
The `Activate` method is called just before the service registration.
In contains runtime information useful for the service. For example
the `activation` parameter contains a session to connect other
services.
The `OnTerminate` method is called just before the service
terminates.
Finally, the `Nanoseconds` method is the implementation of the
timestamp function. Let's start with creating something which computes
timestamps:
// Timestamper creates monotonic timestamps.
type Timestamper time.Time
// Nanoseconds returns the timestamp.
func (t Timestamper) Nanoseconds() (int64, error) {
return time.Since(time.Time(t)).Nanoseconds(), nil
}
Using this `Timestamper` type, let's implements
the `TimestampImplementor` interface:
// timestampService implements TimestampImplementor.
type timestampService struct {
Timestamper // inherits the Nanoseconds method.
}
// Activate is called once the service is online. It provides the
// implementation important runtime informations.
func (t timestampService) Activate(activation bus.Activation,
helper TimestampSignalHelper) error {
return nil
}
// OnTerminate is called when the service is termninated.
func (t timestampService) OnTerminate() {
}
The file clock_stub_gen defines a constructor method for Timestamp
object called `TimestampObject`:
func TimestampObject(impl TimestampImplementor) bus.Actor
It returns a `bus.Actor` type which can be passed to a `bus.Server`.
Let's wrap this function to create a timestamp object based on our
implementation of `TimestampImplementor`:
// NewTimestampObject creates a timestamp object which can be
// registered to a bus.Server.
func NewTimestampObject() bus.Actor {
return TimestampObject(timestampService{
Timestamper(time.Now()),
})
}
That's it. The service implementation is completed. Let's use it in a
`main()` function to start the service.
## Create a program
In order to use the timestamp service, we need a program which uses
`NewTimestampObject` and registers it. The `app` package contains an
helper function for this.
package main
import (
"flag"
"log"
"os"
"os/signal"
"github.com/lugu/qiloop/app"
"github.com/lugu/qiloop/examples/clock"
)
func main() {
flag.Parse()
server, err := app.ServerFromFlag("Timestamp", clock.NewTimestampObject())
if err != nil {
log.Fatal(err)
}
defer server.Terminate()
log.Print("Timestamp service running...")
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// wait until the server fails or is interrupted.
select {
case err = <-server.WaitTerminate():
if err != nil {
log.Fatal(err)
}
case <-interrupt:
log.Print("interrupt, quitting.")
}
}
In order to test it, we need a running instance of QiMessaging. We can
create one with the `qiloop server` command:
$ qiloop server
2019/07/15 22:57:09 Listening at tcp://localhost:9559
Now we can start the timestamp service with:
$ go run ./examples/clock/cmd/service/main.go
2019/07/15 23:00:20 Timestamp service running...
Let double check if the timestamp service is registered to the service
directory using `qiloop info`:
$ qiloop info
[
{
"Name": "ServiceDirectory",
"ServiceId": 1,
"MachineId": "e9b7594a1f209b898e7a3caea5e3199a407cf5bb08d090419e4fffdeddcf167f",
"ProcessId": 17179,
"Endpoints": [
"tcp://localhost:9559"
],
"SessionId": ""
},
{
"Name": "Timestamp",
"ServiceId": 2,
"MachineId": "e9b7594a1f209b898e7a3caea5e3199a407cf5bb08d090419e4fffdeddcf167f",
"ProcessId": 18596,
"Endpoints": [
"unix:///tmp/qiloop-271149288"
],
"SessionId": ""
}
]
Mission completed: a fonctionnal timestamp service! But wait, for a
timestamp to be precise it needs to be locally generated. Let's use
use this service to synchronize a `Timestamper` so we can have precise
and synchronized timestamps.
// SynchronizedTimestamper returns a locally generated timestamp
// source synchronized is the remote Timestamp service.
func SynchronizedTimestamper(session bus.Session) (Timestamper, error) {
ref := time.Now()
constructor := Services(session)
timestampProxy, err := constructor.Timestamp()
if err != nil {
return Timestamper(ref),
fmt.Errorf("reference timestamp: %s", err)
}
delta1 := time.Since(ref)
ts, err := timestampProxy.Nanoseconds()
delta2 := time.Since(ref)
if err != nil {
return Timestamper(ref),
fmt.Errorf("reference timestamp: %s", err)
}
offset := ((delta1 + delta2) / 2) - time.Duration(ts)
return Timestamper(ref.Add(offset)), nil
}
That's it. We have seen how to implement a QiMessaging service and
acces it.
The complete code can be found
[here](https://github.com/lugu/qiloop/blob/master/examples/clock).