Skip to content

Commit

Permalink
change api to closure based and update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
eladb committed Sep 27, 2023
1 parent bc3559c commit 8eca853
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 95 deletions.
18 changes: 6 additions & 12 deletions examples/tests/sdk_tests/service/callbacks.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,21 @@ bring http;

// hack: only supported in the "sim" target for now
if util.env("WING_TARGET") == "sim" {


let b = new cloud.Bucket();
let startCounter = new cloud.Counter();
let status = "status";
let started = "started";
let stopped = "stopped";

class MyServiceHandler impl cloud.IServiceHandler {
pub inflight onStart() {
b.put(status, started);
startCounter.inc();
}
let s = new cloud.Service(inflight () => {
b.put(status, started);
startCounter.inc();

pub inflight onStop() {
return () => {
b.put(status, stopped);
startCounter.dec();
}
}

let s = new cloud.Service(new MyServiceHandler(), autoStart: false);
};
}, autoStart: false);

test "does not start automatically if autoStart is false" {
assert(!b.tryGet(status)?);
Expand Down
20 changes: 20 additions & 0 deletions examples/tests/sdk_tests/service/minimal.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
bring cloud;
bring util;

// hack: only supported in the "sim" target for now
if util.env("WING_TARGET") == "sim" {

let s = new cloud.Service(inflight () => {
log("hello, service!");

return () => {
log("stopping!");
};
});

test "start and stop" {
assert(s.started());
s.stop();
assert(!s.started());
}
}
50 changes: 19 additions & 31 deletions examples/tests/sdk_tests/service/stateful.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ bring http;

// hack: only supported in the "sim" target for now
if util.env("WING_TARGET") == "sim" {
class MyService impl cloud.IServiceHandler {
class MyService {
b: cloud.Bucket;
body: str;

Expand All @@ -15,41 +15,29 @@ if util.env("WING_TARGET") == "sim" {
this.body = body;

// it's idiomatic to just pass `this` here and implement the callbacks on the current object.
this.s = new cloud.Service(this);
}

inflight var state: num;

inflight init() {
this.state = 123;
}

pub inflight onStart() {
log("starting service");
util.sleep(1s);
this.b.put("ready", "true");
let port = MyService.startServer(this.body);
log("listening on port ${port}");
this.state = 456;
this.b.put("port", "${port}");
}

pub inflight onStop() {
log("stopping service");
log("state is: ${this.state}");

// make sure inflight state is presistent across onStart/onStop
assert(this.state == 456);
MyService.stopServer();
this.s = new cloud.Service(inflight () => {
log("starting service");
util.sleep(1s);
this.b.put("ready", "true");
let port = MyService.startServer(this.body);
log("listening on port ${port}");
let state = 456;
this.b.put("port", "${port}");

return () => {
log("stopping service");
log("state is: ${state}");

// make sure inflight state is presistent across onStart/onStop
assert(state == 456);
MyService.stopServer();
};
});
}

pub inflight access() {
// when access() is called we expect the service to have completed initialization
this.b.get("ready");

// this state belongs to the inflight client created for the test cloud function
// and not to the service, so we expect it to stay 123.
assert(this.state == 123);
}

pub inflight port(): num {
Expand Down
84 changes: 48 additions & 36 deletions libs/wingsdk/src/cloud/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,86 +16,98 @@ sidebar_position: 1

The `cloud.Service` class represents a cloud service that has a start and optional stop lifecycle.

Services are a common way to define long running code, such as web servers and custom daemons.
Services are a common way to define long running code, such as microservices.

## Usage

### Creating a service

When defining a service, the first argument is an inflight closure that represents
the service handler. This handler is responsible to perform any initialization
activity and **return asynchronously** when initialization is complete.

```js
bring cloud;

// At minimum a service needs to have an onStart handler.
let service = new cloud.Service(
onStart: inflight() => {
log("Service started...");
}
);
new cloud.Service(inflight () => {
// ...
// kick off any initialization activities asynchronously
// ...
log("Service started...");
});
```

### Disable auto-start

By default the service resource will start automatically, however this can be disabled by
passing `autoStart: false` to the constructor.
By default the service resource will start automatically, however this can be disabled by passing
`autoStart: false` to the constructor.

```js
bring cloud;

let service = new cloud.Service(
autoStart: false,
onStart: inflight() => {
log("Service started...");
}
);
let handler = inflight () => {
log("service started...");
};

let service = new cloud.Service(handler, autoStart: false);
```

### Defining service with stop behavior
### Service cleanup

Optionally, the service handler inflight closure can return another inflight closure which will be
called when the service is stopped. Using a return closure allows naturally passing context between
the async calls.

```js
bring cloud;

let service = new cloud.Service(
onStart: inflight() => {
log("Service started...");
},
onStop: inflight() => {
new cloud.Service(inflight() => {
let server = startHttpServer();
log("Service started...");
return () => {
log("Service stopped...");
},
);
server.close();
};
});
```

### Stopping and starting a service

The inflight methods `start` and `stop` are used exactly how they sound, to stop and start the service.
Here is an example of using a service that will track how often it is started and stopped using counters.
An important aspect to note is that consecutive starts and stops have no affect on a service. For example
if a `service.start()` is called on a service that is already started, nothing will happen.
The inflight methods `start()` and `stop()` are used exactly how they sound, to stop and start the
service. The method `started()` returns a `bool` indicating if the service is currently started.

Here is an example of using a service that will track how often it is started and stopped using
counters.

An important aspect to note is that consecutive starts and stops have no affect on a service. For
example, if a `service.start()` is called on a service that is already started, nothing will happen.

```js
bring cloud;

let startCounter = new cloud.Counter() as "start counter";
let stopCounter = new cloud.Counter() as "stop counter";

let service = new cloud.Service(
autoStart: false,
onStart: inflight() => {
let i = startCounter.inc();
log("Service started for the ${i}th time...");
},
onStop: inflight() => {
let handler = inflight() => {
let i = startCounter.inc();
log("Service started for the ${i}th time...");
return () => {
let i = stopCounter.inc();
log("Service stopped for the ${i}th time...");
},
);
};
};

let service = new cloud.Service(handler, autoStart: false);

// Functions to stop and start the service
new cloud.Function(inflight() => {
service.start();
assert(service.started());
}) as "start service";

new cloud.Function(inflight() => {
service.stop();
assert(!service.started());
}) as "stop service";
```

Expand Down
60 changes: 47 additions & 13 deletions libs/wingsdk/src/cloud/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export abstract class Service extends Resource implements IInflightHost {

// indicates that we are calling the inflight constructor and the
// inflight "handle" method on the handler resource.
handler._registerBind(this, ["onStart", "onStop", "$inflight_init"]);
handler._registerBind(this, ["handle", "$inflight_init"]);

const inflightClient = handler._toInflight();
const lines = new Array<string>();
Expand All @@ -86,12 +86,8 @@ export abstract class Service extends Resource implements IInflightHost {
lines.push(" return $obj;");
lines.push("};");

lines.push("exports.onStart = async function() {");
lines.push(" return (await $initOnce()).onStart();");
lines.push("};");

lines.push("exports.onStop = async function() {");
lines.push(` return (await $initOnce()).onStop();`);
lines.push("exports.handle = async function() {");
lines.push(" return (await $initOnce()).handle();");
lines.push("};");

const assetName = ResourceNames.generateName(this, {
Expand Down Expand Up @@ -130,7 +126,11 @@ export abstract class Service extends Resource implements IInflightHost {

/** @internal */
public _getInflightOps(): string[] {
return [ServiceInflightMethods.START, ServiceInflightMethods.STOP];
return [
ServiceInflightMethods.START,
ServiceInflightMethods.STOP,
ServiceInflightMethods.STARTED,
];
}
}

Expand All @@ -148,11 +148,18 @@ export interface IServiceClient {
* @inflight
*/
start(): Promise<void>;

/**
* Stop the service
* @inflight
*/
stop(): Promise<void>;

/**
* Indicates whether the service is started.
* @inflight
*/
started(): Promise<boolean>;
}

/**
Expand All @@ -172,16 +179,42 @@ export interface IServiceHandlerClient {
*
* DO NOT BLOCK! This handler should return as quickly as possible. If you need to run a long
* running process, start it asynchronously.
*
*
* @returns an optional function that can be used to cleanup any resources when the service is
* stopped.
*
* @example
*
* bring cloud;
*
* new cloud.Service(inflight () => {
* log("starting service...");
* return () => {
* log("stoping service...");
* };
* });
*
*/
onStart(): Promise<void>;
handle(): Promise<IServiceStopHandler | undefined>;
}

/**
* @inflight `@winglang/sdk.cloud.IServiceStopHandlerClient`
*/
export interface IServiceStopHandler extends IResource {}

/**
* Inflight client for `IServiceStopHandler`.
*/
export interface IServiceStopHandlerClient {
/**
* Handler to run in order to stop the service. This is where you implement the shutdown logic of
* the service, stop any activities, and clean up any resources.
* Handler to run when the service stops. This is where you implement the cleanup logic of
* the service, stop any activities asychronously.
*
* @default - no special activity at shutdown
* @inflight
*/
onStop?(): Promise<void>;
handle(): Promise<void>;
}

/**
Expand All @@ -191,4 +224,5 @@ export interface IServiceHandlerClient {
export enum ServiceInflightMethods {
START = "start",
STOP = "stop",
STARTED = "started",
}
Loading

0 comments on commit 8eca853

Please sign in to comment.