Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overwrite route default #9

Merged
merged 9 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 242 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ The `id` field is an auto-generated integer with the length specified by the `SE
### Example

Say we register a service `New York` with the following definition:

`[POST] http://localhost:10311/rincon/services`

```json
{
"name": "New York",
Expand Down Expand Up @@ -150,10 +153,248 @@ Any `2xx` status code will mark the heartbeat as successful. Any other response

### Client Heartbeats

When operating in client heartbeat mode, Rincon expects pings from the registered services at least once every heartbeat interval. This ping should be made to the service registration endpoint, with all the service fields correctly populated. The ping can be cofirmed by ensuring the `updated_at` field in the response has increased. At each heartbeat interval, Rincon will remove all services with an updated_at timestamp older than the interval.
When operating in client heartbeat mode, Rincon expects pings from the registered services at least once every heartbeat interval. This ping should be made to the service registration endpoint, with all the service fields correctly populated. The ping can be cofirmed by ensuring the `updated_at` field in the response has increased. At each heartbeat interval, Rincon will remove all services with an `updated_at` timestamp older than the interval.

## Routing

Services register routes to tell Rincon what requests they can handle. Each route has the following properties:

- `id`: The unique identifier for the route.
- `route`: The actual route path.
- `method`: The http methods associated with the route.
- `service_name`: The service associated with the route.

When registering a route, you must provide the route, method, and service associated with that route. Upon registration, Rincon will return the following Route object.

```json
{
"id": "/rincon/ping-[*]",
"route": "/rincon/ping",
"method": "*",
"service_name": "rincon",
"created_at": "2024-08-04T01:05:37.262645179-07:00"
}
```

The `id` is a generated field in the format `route-[method]`. Note that routes are tied to services, not any specific instance of a service. So if we register 10 routes to the `new_york` service and then spin up 2 more instances of the `new_york` service, all instances of the `new_york` service are considered able to handle those 10 routes.

### Supported Methods

Rincon currently supports the following HTTP Methods:
- `GET`
- `POST`
- `PUT`
- `DELETE`
- `PATCH`
- `OPTIONS`
- `HEAD`
- `*` (wilcard, route can handle all methods)

### Wildcard Routes

Dynamic routing is supported through the use of wildcards, enabling services to handle variable path segments efficiently.

#### `/*` - Any Wildcard

This wildcard can be used to allow any string to match to a certain path segment.

```
/users/*
---
/users/123
/users/abc
```

You can also use this wildcard in the middle of your route.

```
/users/*/profile
---
/users/123/profile
/users/abc/profile
```

#### `/**` - All Wildcard

This wildcard can be used to allow any string to match to all remaining path segments.

```
/users/**
---
/users/123
/users/abc/edit
/users/a1b2c3/account/settings
```

You can even use both wildcards for more specific routing.

```
/users/*/profile/**
---
/users/123/profile/edit
/users/abc/profile/settings/notifications
```

> [!WARNING]
> While you can have the all wildcard (`**`) in the middle of a route path, when the route graph is computed all proceeding segments are ignored.
> ```
> /users/**/profile
> ---
> /users/123
> /users/abc/profile
> /users/a1b2c3/profile/edit
> ```

### Stacking Routes

Routes with the same path and service name will automatically be "stacked". This just means that their methods will be combined into one registration in Rincon.

```c
New York: /users [GET]
New York: /users [POST]
---
New York: /users [GET,POST]
```

If the existing or new route method is `*`, then the stacked method will simply be the wildcard method.

```c
New York: /users [*]
New York: /users [POST]
---
New York: /users [*]
```

### Conflicting Route Registrations

By default, `OVERWRITE_ROUTES` is set to `false`. This means that if you attempt to register a route that has a conflict with an existing route, it will be rejected. Usually these conflicts arise from two routes attached to different services having an overlap in their methods.

```c
New York: /users [GET]
San Francisco: /users [GET] // cannot be registered
```

Even if the newer route has a higher method coverage than the existing route, the registration will be rejected as long as `OVERWRITE_ROUTES` is set to `false`.

```c
New York: /users [GET]
San Francisco: /users [GET,POST] // cannot be registered
```

To ensure that your routes are registered successfully, make sure there are no method overlaps.

```c
New York: /users [GET]
San Francisco: /users [POST,PUT] // no conflict, will be registered successfully!
```

### Overwriting Routes

When `OVERWRITE_ROUTES` is set to `true`, any conflicting registrations will not be rejected. Instead the new registration will replace the existing one.

```c
New York: /users [GET]
San Francisco: /users [GET]
---
San Francisco: /users [GET]
```

If there are multiple conflicting routes, they will all be replaced.

```c
New York: /users [GET]
Boston: /users [POST]
Los Angelos /users [DELETE]
San Francisco: /users [GET,POST,DELETE]
---
San Francisco: /users [GET,POST,DELETE]
```

> [!CAUTION]
> Existing routes will be replaced even if they have a higher route coverage than the new route. Be careful when overwriting routes!
> ```c
> New York: /users [GET,POST]
> San Francisco: /users [GET]
> ---
> San Francisco: /users [GET] // route for New York was completely removed!
> ```

### Route Matching

Internally, Rincon computes a route graph to help it match a requested route against its registered routes. This is a simple directed acyclic graph where nodes are route paths and edges are slugs needed to get to the next route path. Nodes also contain information about which services and methods can be handled at that route path.

As an example, let's say we have the following route registrations.

```
New York: /users
New York: /users/*
San Francsico: /users/*/notifications
```

### Example

Using our `New York` service from the previous example, let's register the following route.

`[POST] http://localhost:10311/rincon/routes`

```json
{
"route": "/users",
"method": "*",
"service_name": "New York",
}
```

Rincon will return the following route object to us.

```json
{
"id": "/users-[*]",
"route": "/users",
"method": "*",
"service_name": "new_york",
"created_at": "2024-08-27T14:04:43.688527-07:00"
}
```

Now we can confirm our route was correctly registered by making a request to the route matching endpoint.

`[GET] http://localhost:10311/rincon/match?route=users&method=GET`

```json
{
"id": 416156,
"name": "new_york",
"version": "1.0.0",
"endpoint": "http://localhost:3000",
"health_check": "http://localhost:3000/health",
"updated_at": "2024-08-27T14:04:23.172214-07:00",
"created_at": "2024-08-27T14:04:23.172214-07:00"
}
```

As expected, Rincon returned our `New York` service definition. Now let's try to register a route for a different service (assume that `San Francisco` is a service that has already been registered with Rincon).

`[POST] http://localhost:10311/rincon/routes`

```json
{
"route": "/users",
"method": "POST",
"service_name": "San Francisco",
}
```

This time, we get the following error from Rincon.

```json
{
"message": "route with id /users-[POST] overlaps with existing routes [[*] /users (new_york)]"
}
```

This is because `New York` was already registered to handle all methods (based on the `*` wildcard method definition) on the `/users` route. By default a new service cannot register a route with a conflicting method. This can be changed by setting `OVERWRITE_ROUTES`.

## Load Balancing

## Configuration
Expand Down
4 changes: 2 additions & 2 deletions utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ func VerifyConfig() {
SugarLogger.Infoln("STORAGE_MODE is not set, defaulting to local")
}
if config.OverwriteRoutes == "" {
config.OverwriteRoutes = "true"
SugarLogger.Debugln("OVERWRITE_ROUTES is not set, defaulting to true")
config.OverwriteRoutes = "false"
SugarLogger.Debugln("OVERWRITE_ROUTES is not set, defaulting to false")
}
if config.HeartbeatType == "" {
config.HeartbeatType = "server"
Expand Down
Loading