README.md 14.7 KB
Newer Older
Corey O'Connor's avatar
Corey O'Connor committed
1
glngn server is an extensible business process application server. Similar to
Corey O'Connor's avatar
Corey O'Connor committed
2
Microsoft Access or Apple FileMaker, but designed for event sourced business services with a code-first philosophy.
Corey O'Connor's avatar
Corey O'Connor committed
3
A standalone application with an approachable extension SDK is provided.
Corey O'Connor's avatar
Corey O'Connor committed
4
Applications developed with glngn server can be easily deployed to a kubernetes cluster for sharing.
5

Corey O'Connor's avatar
~  
Corey O'Connor committed
6
- Support available from the [DogHeadBone LLC](mailto:support@dogheadbone.com) company
Corey O'Connor's avatar
Corey O'Connor committed
7
- Useful built-in services
8
- Easy event captures
Brett O'Connor's avatar
Brett O'Connor committed
9
- Predictable REST interface with an OpenAPI schema
10
- A [high level extension API](http://docs.glngn.com) for Scala service developers.
11 12 13
  (Plain, typed akka and zio interfaces are provided.)
- Simple persistence features
- Simple Kubernetes (k8s) cluster deployments. In addition to some custom automation, akka
Corey O'Connor's avatar
~  
Corey O'Connor committed
14
  management tools are included. OpenShift is also supported.
15

Corey O'Connor's avatar
Corey O'Connor committed
16
Sound good so far? Excellent! Let's start with basic, interactive usage of the standalone application.
17

18 19
# Developer Guide

20
This document is focused on usage and not extension of the application. Developers should read:
21

Corey O'Connor's avatar
Corey O'Connor committed
22 23
- [development guide](docs/Customization.md)
- [development error help](docs/CompileErrorHelp.md)
24

25 26 27
The implementation and extension API is familiar to Scala service developers. To a large degree,
glngn server is a friendly, constrained, pre-configured typed akka plus ZIO server.  A few of the
niceties included:
28 29

- Cluster is enabled and established without additional configuration.
Corey O'Connor's avatar
Corey O'Connor committed
30
- Logging and data marshalling are configured appropriately.
31
- Additional error tracking and handling.
32

Corey O'Connor's avatar
~  
Corey O'Connor committed
33
# Standalone Application
34

Corey O'Connor's avatar
~  
Corey O'Connor committed
35
prerequisites:
Corey O'Connor's avatar
Corey O'Connor committed
36

Corey O'Connor's avatar
Corey O'Connor committed
37
- java runtime version 8 or above.
Corey O'Connor's avatar
Corey O'Connor committed
38
- agreement to the [Developer License](docs/Developer%20License%20v1.rtf?inline=false) This license is also included in the jar archive.
39 40 41

## Download

Corey O'Connor's avatar
Corey O'Connor committed
42
Prior to downloading, read and agree to the [Developer License agreement](docs/Developer%20License%20v1.rtf?inline=false).
Corey O'Connor's avatar
~  
Corey O'Connor committed
43

jenkins's avatar
jenkins committed
44
- download [the latest release](https://glngn-server-releases.s3.amazonaws.com/assemblies/glngn/glngn-server-assembly_2.12/0.3.189/jars/glngn-server-assembly_2.12-assembly.jar)
45 46

```bash
jenkins's avatar
jenkins committed
47
curl -L -o glngn-server-assembly.jar https://glngn-server-releases.s3.amazonaws.com/assemblies/glngn/glngn-server-assembly_2.12/0.3.189/jars/glngn-server-assembly_2.12-assembly.jar
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
```

## Command Help

Run using java and pass `--help` to see usage information:

```bash
$ java -jar ./glngn-server-assembly.jar --help
Usage: glngn-server [options] [command] [command options]
  --port  <int?>
  --disable-ops  <bool>

Commands: print-config; print-full-config; run; version

Command: print-config
Usage: glngn-server print-config

Command: print-full-config
Usage: glngn-server print-full-config

Command: run
Usage: glngn-server run
  --state-dir  <string?>
  --exit-immediately  <bool>
  --initial-node-count  <int>
  --heap-dump-on-init  <bool>

Command: version
Usage: glngn-server version

Corey O'Connor's avatar
Corey O'Connor committed
78
Use of this software is agreement with the included Application Developer License.
79 80 81 82
```

## Running

83 84 85
Note: For consistency within the docs, the port 10000 is requested using `--port 10000`. If this option is
not provided a free port will automatically be selected.

Corey O'Connor's avatar
Corey O'Connor committed
86
Let's run a server configured to store data at `./starter.state`. In a terminal session:
87 88 89

```bash
$ java -jar ./glngn-server-assembly.jar --port 10000 run --state-dir ./starter.state
90
INFO  use of this software is agreement with the included Developer License.
91
...
Corey O'Connor's avatar
~  
Corey O'Connor committed
92
[lots of output]
93
...
Corey O'Connor's avatar
~  
Corey O'Connor committed
94
INFO  glngn.server.host.ServerApp$  - application is listening at http://localhost:10000
95 96
```

97 98
At this point the standalone server is initialized and ready.

Corey O'Connor's avatar
Corey O'Connor committed
99
See [docs/Deep Dives](https://gitlab.com/glngn/glngn-server-examples/blob/master/docs/DeepDives.md)
Corey O'Connor's avatar
Corey O'Connor committed
100
for more details on what happened.
Corey O'Connor's avatar
Corey O'Connor committed
101

Corey O'Connor's avatar
~  
Corey O'Connor committed
102 103 104 105
Hit control-C, or TERM the process (there is only one), to shut down the server. This will take a bit
for a nice, coordinated shutdown. Which is preferred. That said, the server is configured to attempt
recovery from sudden terminations. Such as killing a container in a kubernetes cluster.

Corey O'Connor's avatar
Corey O'Connor committed
106
## Usage
Corey O'Connor's avatar
Corey O'Connor committed
107

Corey O'Connor's avatar
Corey O'Connor committed
108 109 110
The glngn server provides a REST API with an [OpenAPI](https://www.openapis.org/) specification.
This API supports the built-in features of glngn server as well as any additional features added
by extensions.
Corey O'Connor's avatar
Corey O'Connor committed
111

Corey O'Connor's avatar
Corey O'Connor committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
If you are looking for a GUI for glngn server [please let us know](mailto:support@dogheadbone.com).

### OpenAPI schema

Let's query for the API schema to see what endpoints there are:

```bash
$ curl http://localhost:10000/openapi.json
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "dynamic",
    "version" : "0.1"
  },
  "paths": {
    "/healthz": ...
    "/_ops/groups": ...
129
    "/_ops/services": ...
Corey O'Connor's avatar
Corey O'Connor committed
130 131 132 133 134 135 136 137 138 139
[...]
  }
}
[...]
```

A brief overview:

1. The `/healthz` route. We'll try that one below.
2. The `/_ops/groups` route. This is an operations route for the groups in glngn server. All services
140
are placed under a group. The default configuration does not include any groups. This
Corey O'Connor's avatar
Corey O'Connor committed
141
will be the first route we try in the `API Tour`.
142 143
3. The `/_ops/services` route. This is an operations route to query the services that are available
to instantiate under a group. This will be covered after the `/_ops/groups` route.
Corey O'Connor's avatar
Corey O'Connor committed
144 145 146 147 148

### Multiple Representations

The `/healthz` route will only return status 200 OK if the server is able to handle requests.
This is also a nice example to demonstrate how glngn server supports multiple representations:
Corey O'Connor's avatar
Corey O'Connor committed
149 150

```bash
151
$ curl http://localhost:10000/healthz.txt
Corey O'Connor's avatar
Corey O'Connor committed
152 153 154
OK
```

Corey O'Connor's avatar
Corey O'Connor committed
155 156
Note the use of a `.txt` extension to request a text representation. We could have
requested another representation, such as `json`, explicitly or implicitly:
Corey O'Connor's avatar
Corey O'Connor committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

```bash
$ curl -H 'Accept: text/plain' localhost:10000/healthz
OK

$ curl -H 'Accept: application/json' localhost:10000/healthz
{
  "memStats" : {
[...]
  "ok" : true
}
$ curl localhost:10000/healthz.json
{
  "memStats" : {
[...]
  "ok" : true
}
```

This pattern is supported throughout: Endpoints will have suffixed versions that imply the expected
representation.

Corey O'Connor's avatar
Corey O'Connor committed
179
### API Tour
Corey O'Connor's avatar
~  
Corey O'Connor committed
180

Corey O'Connor's avatar
Corey O'Connor committed
181 182
The API conventions enforced by glngn server:

Corey O'Connor's avatar
Corey O'Connor committed
183 184 185 186
- All routes prefixed with a `_` are operations routes. These are provided by glngn server.
- `/_ops/`
    - Deployment `_ops` routes
    - These are always available
Corey O'Connor's avatar
Corey O'Connor committed
187
    - EG: `/_ops/groups`: query and modify the available groups
Corey O'Connor's avatar
Corey O'Connor committed
188
- `/$GROUP/_ops`
Corey O'Connor's avatar
Corey O'Connor committed
189
    - Group `_ops` routes. These require a group with id, `$GROUP`, to be created
Corey O'Connor's avatar
Corey O'Connor committed
190
- `/$GROUP/$SERVICE/_ops`
Corey O'Connor's avatar
Corey O'Connor committed
191
    - Service fragment `_ops` routes, `/$GROUP/$SERVICE/_ops`, require the service fragment to be
Corey O'Connor's avatar
Corey O'Connor committed
192
    instantiated under the group
Corey O'Connor's avatar
Corey O'Connor committed
193
    - automatically generated for each instantiated service fragment
Corey O'Connor's avatar
Corey O'Connor committed
194
- `/$GROUP/$SERVICE/...`
Corey O'Connor's avatar
Corey O'Connor committed
195
    - All other routes under a service are provided by a Service Fragment's `endpoints`
Corey O'Connor's avatar
Corey O'Connor committed
196

197
#### 1. Create a Group
Corey O'Connor's avatar
Corey O'Connor committed
198

199 200 201
The `/_ops/groups` endpoint is used to query the existing groups and create new groups.  All services
are namespaced under a group. Before we can instantiate and use a service we must first create a
group:
202

Corey O'Connor's avatar
Corey O'Connor committed
203 204 205
~~~
$ curl --data '{ "id": "accounts", "name": "Client Accounts" }' localhost:10000/_ops/groups
{
Corey O'Connor's avatar
Corey O'Connor committed
206
  "enabled" : true,
Corey O'Connor's avatar
Corey O'Connor committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
  "id" : {
    "txt" : "accounts"
  },
  "name" : "Client Accounts",
  "operators" : null
}
~~~

This creates a group with the logical name `accounts`. The endpoint responds with the
current definition of the group.

Which we can confirm by querying the `/_ops/groups` endpoint again:

~~~
$ curl localhost:10000/_ops/groups
{
  "accounts" : {
Corey O'Connor's avatar
Corey O'Connor committed
224
    "enabled" : true,
Corey O'Connor's avatar
Corey O'Connor committed
225 226 227
    "id" : {
      "txt" : "accounts"
    },
228
    "name" : "Client Accounts",
Corey O'Connor's avatar
Corey O'Connor committed
229 230 231 232 233 234 235 236
    "operators" : null
  }
}
~~~

Note the `operators` attribute. Using the premium license server this would contain the users
authorized to access that group. The free license server ignores this attribute.

Corey O'Connor's avatar
Corey O'Connor committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
#### Groups Extend the API

By creating a group we've extended the provided API. Query the `openapi.json` route again
and note the `/accounts/_ops` route:

~~~
$ curl localhost:10000/openapi.json
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "dynamic",
    "version" : "0.1"
  },
  "paths" : {
    "/accounts/_ops" : {
      "get" : {
        "operationId" : "getAccounts_ops",
        "responses" : {
          "200" : {
            ...
          }
        }
      }
    }
  }
}
~~~

##### Note: Groups Persist

267 268
If you were to restart the `java -jar ./glngn-server-assembly.jar` process note the group
remains. `glngn-server` persists the created group to the provided state storage.
Corey O'Connor's avatar
Corey O'Connor committed
269

270
In the example above the state storage is the `starter-state` directory provided to the
Corey O'Connor's avatar
Corey O'Connor committed
271 272 273
`--state-dir` option.

#### Instantiate a Service Fragment
Corey O'Connor's avatar
~  
Corey O'Connor committed
274

275 276 277 278 279 280 281 282 283 284 285 286 287
Now that a group exists we will instantiate a service under that group. This will:

1. Extend the API to include that service.
2. Persist the data for that service under the group and service namespace.

##### 1. Query for available services

To determine what services fragments we can instantiate query the `/_ops/services` route:

~~~

$ curl localhost:10000/_ops/services.json
[
288
  "glngn.server.services.StringValues$",
289
  "glngn.server.services.IntegerCounters$"
290 291 292 293 294 295 296 297 298
]
~~~

This provides the list of logical IDs of service fragments that can be instantiated under a group.
This set can be customized using the developer SDK. We are using the default glngn distribution which
includes a standard set of generic service fragments.

##### 2. Instantiate the service fragment

299
Let's pick the `glngn.server.services.IntegerCounters$` service fragment to instantiate.
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

After creating the group `accounts` a route was added `/accounts/_ops/services`. According to the
OpenAPI spec this route accepts a `POST` of a `GroupServiceChange` json structure. The schema of
this structure is:

~~~
"GroupServicesChange" : {
    "required" : [
        "serviceId",
        "logicalServiceId"
    ],
    "type" : "object",
    "properties" : {
        "serviceId" : {
            "type" : "string"
        },
        "logicalServiceId" : {
            "type" : "string"
        }
    }
}
~~~

We already know the logical service ID (`logicalServiceId`) from the query to `/_ops/services`. The
`serviceId` is an identifier we can pick ourselves. We will pick `retail` for this example.

~~~
327
$ curl --data '{ "serviceId": "retail", "logicalServiceId": "glngn.server.services.IntegerCounters$" }' localhost:10000/accounts/_ops/services
328 329
{
  "retail" : [
330
    "glngn.server.services.IntegerCounters$"
331 332 333 334 335 336
  ]
}
~~~

Which replies with the new set of services under that group. This reply states the `retail` service
is a composition of a list of service fragments. Which only contains
337
`glngn.server.services.IntegerCounters$` in this example.
Corey O'Connor's avatar
Corey O'Connor committed
338

339 340 341
See [docs/Deep Dives](https://gitlab.com/glngn/glngn-server-examples/blob/master/docs/DeepDives.md) for 
details.

Corey O'Connor's avatar
Corey O'Connor committed
342
#### Use a Service
Corey O'Connor's avatar
Corey O'Connor committed
343

344 345 346
With a service fragment instantiated under a group we can note the OpenAPI schema now includes routes
for the `/accounts/retail` service:

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
##### The Extended OpenAPI Schema

With this fragment instantiated under `/accounts/retail` the service's routes are now visible in the 
OpenAPI schema:

~~~
$ curl localhost:10000/openapi.json
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "dynamic",
    "version" : "0.1"
  },
  "paths" : {
    "/accounts/retail/{Entity ID}" : {
      "get" : {
          ...
        }
      }
    },
    "/accounts/retail/{Entity ID}/inc" : {
      "post" : {
          ...
        }
      }
    },
    "/accounts/retail/{Entity ID}/dec" : {
      "post" : {
          ...
        }
      }
    }
  }
}

~~~

- `/accounts/retail/{Entity ID}`
    - A query, `GET`, that returns an integer given an `Entity ID`
- `/accounts/retail/{Entity ID}/inc`
    - A command, `POST`, that returns the new value of a given `Entity ID` when provided an increment amount.
- `/accounts/retail/{Entity ID}/dec`
    - A command, `POST`, that returns the new value of a given `Entity ID` when provided a decrement amount.
Corey O'Connor's avatar
Corey O'Connor committed
390

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
##### Note: Entity ID

An `Entity ID` is a common path component and identifier in glngn server. All `Entity ID`s are:

- case insensitive 
- case preserving
- consist only of characters in `[a-zA-Z0-9\\-_]`: Upper or lower case letters, numbers, '-' and '_'.
- all invalid characters are filtered if provided for an Entity ID
- no longer than 28 characters

Adding new service fragments using the glngn server with the [Developer SDK](docs/Customization.md) 
can be used to constrain the domain of identifiers.

##### Get a Counter

~~~
$ curl localhost:10000/accounts/retail/central-store
{
  "value" : 0
}
~~~

This is a query for the `retail` counter `central-store`. The result is a value of 0.

Creating the counter prior to querying was not required in this case. Counters default to 0. This
is a useful default and customizable with the [Developer SDK](docs/Customization.md).

##### Increment a Counter

Using the `inc` subroute the counter can be incremented by a given value:

~~~
$ curl --data 10 localhost:10000/accounts/retail/central-store/inc
{
  "value" : 10
}
$ curl --data 12 localhost:10000/accounts/retail/central-store/inc
{
  "value" : 22
}
$ curl localhost:10000/accounts/retail/central-store
{
  "value" : 22
}
~~~

Corey O'Connor's avatar
Corey O'Connor committed
437
##### Events Persist
438 439 440 441 442 443 444 445 446 447 448 449

If the glngn server application was to be restarted then this data would persist. If the counter
was queried after restart the value would be the same as before. 

The server is persisting all events that modified that counter. Each of those `inc` and `dec` 
used to change a counter is saved. This history is essential to "event sourcing". Having a
history, not only the current state, of a service enables a new level of analysis of your 
business activity.

The standalone version persists to the directory provided to the `--state` command line argument.
When deployed to kubernetes the service can also persist to a database such as Amazon's DynamoDB
or Cassandra.
Corey O'Connor's avatar
Corey O'Connor committed
450

Corey O'Connor's avatar
Corey O'Connor committed
451
#### Capturing Events
Corey O'Connor's avatar
Corey O'Connor committed
452

453 454 455 456 457 458
If we have a model of our business events we can build and instantiate service fragments that 
reflect that model. What do we do if we don't have a model of our business events? These unknowns are likely
in a number of situations: New business proposals; exceptional business events; unexpected issues. These
will not have a schema or service structure a-priori. This is why glngn server supports arbitrary
events to enable your business to start building a model.

Corey O'Connor's avatar
Corey O'Connor committed
459 460 461
TODO:

1. demo capturing unmodeled events:
Corey O'Connor's avatar
Corey O'Connor committed
462
    1. deployment-wide
Corey O'Connor's avatar
Corey O'Connor committed
463 464 465
    2. group events
    3. service events
2. demo capturing a service modeled event
Corey O'Connor's avatar
Corey O'Connor committed
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496

#### Requests

A request is a unique interaction with a client. Requests are implicitly
created when the endpoints are used. A request can be explicitly requested
by posting an event of type `command` or `query` to the `_ops` endpoints.

TODO:

1. Each request has a unique ID
2. Any event may be associated with a request
3. A request is resolved when a required event is provided
4. Events may still be associated with a request after resolution

#### Event History

All services have an event history.

TODO:

1. demo querying service history

#### Request History

Each request has an event history.

TODO:

1. demo querying request history