Skip to content
This repository has been archived by the owner on May 8, 2020. It is now read-only.

Commit

Permalink
feat(documentation): Added doc pages for services and migrations, sta…
Browse files Browse the repository at this point in the history
…rted on seeders
  • Loading branch information
zakhenry committed Jul 6, 2016
1 parent 9491a85 commit a542124
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 3 deletions.
63 changes: 63 additions & 0 deletions docs/guide/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,66 @@ layout: guide.hbs
pendingTask: https://github.com/ubiquits/ubiquits/issues/25
---

## Overview
Migrations are classes that represent a change in the database schema. They can either be a completely new table creation,
or changes to tables like column renaming, addition, deletion, moving of data from one table to another.

The important feature is that a migration should be reversible. This allows a mistake in a migration to be reversed safely
without breaking the app.

Sometime data loss is unavoidable, for example if you were to drop a column, the rollback can't recreate that lost data,
but if the migration is reversed, the column will be recreated as it was, just without data.

## Registration
Migrations can be registered using the `@Migration()` class decorator, and by extending the `AbstractMigration` abstract
class, which provides the common interface for `migrate()` and `rollback()`

Migrations classes must be imported by the server `main.ts` so that the decorator is invoked, registering the migration
with the `EntityRegistry`.

Example `./src/server/migrations/updateUsersUsername.migration.ts`:
```typescript
import { AbstractMigration, Database } from '@ubiquits/core/server';
import { Migration, Logger } from '@ubiquits/core/common';

@Migration()
export class UpdateUsersUsernameColumnMigration extends AbstractMigration {

constructor(logger:Logger, database:Database){
super(logger, database);
}

public migrate(): Promise<void> {
return this.database.query(`ALTER TABLE users CHANGE username user VARCHAR(6) NOT NULL DEFAULT ''`);
}

public rollback():Promise<void> {
return this.database.query(`ALTER TABLE users CHANGE user username VARCHAR(6) NOT NULL DEFAULT ''`);
}

}
```

Example `./src/server/migrations/index.ts`:
```typescript
export * from './updateUsersUsername.migration';
```

Example `./src/server/main.ts`:
```typescript
import { bootstrap } from '@ubiquits/core/server';
import * as migrations from './migrations';

export { BootstrapResponse };
export default bootstrap([migrations], []);

```

## Current Status
Migrations are currently a work in progress. They are current run immediately, and in parallel on bootstrap, which is ok for localhost
rapid development, but won't work in production.

The plan is to have the migrations register then have the `RemoteCli` pick up which migrations need to be run, and when
the user logs in to the cli runtime they will be prompted for which migrations to run.

See the [github issue](https://github.com/ubiquits/ubiquits/issues/25) for more info
3 changes: 2 additions & 1 deletion docs/guide/seeding.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ date: 2016-06-09
collection: guide
collectionSort: 1
layout: guide.hbs
pendingTask: true
---

## Overview
Seeders are classes that define mock data to fill the database with for development and QA purposes.
97 changes: 95 additions & 2 deletions docs/guide/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,100 @@ date: 2016-06-09
collection: guide
collectionSort: 1
layout: guide.hbs
pendingTask: true
---

This has been largely completed, the documentation just needs writing
## Intro
Services are general-purpose singleton classes that can be injected by Controllers, Middleware, other services etc.
A service can do anything from interacting with a third party API, to manipulating the local filesystem, to handling
complex calculations.

It reasonable to say that a service is anything that doesn't fit into the more rigid definitions of the other entities.

## Registration
### Basic
If a service has no special startup requirements, it can simply be decorated with `@Injector()`, then be injected somewhere.

Example:
`./src/common/services/example.service.ts`
```typescript
import { Injectable } from '@angular/core';
import { Logger } from '@ubiquits/core/common';

@Injectable()
export class ExampleService {

protected logger: Logger;

constructor(loggerBase: Logger) {
this.logger = loggerBase.source('Example Service');
}

public logTest(message: string): void {
this.logger.debug(message);
}

}
```
This service can be injected into any Controller by simply typehinting the service name:

```typescript
constructor(protected exampleService: ExampleService) {}
```
The class will have a singleton instance of the `ExampleService` ready for action at `this.exampleService`

### Bootstrap blocking
For more specialist services, the `@Service()` decorator is available which registers the service with the `EntityRegistry`,
and is specially handled in the bootstrapper to defer bootstrapping of any other entity types until the `Service.initialize()`
promise is resolved.

These services should both be decorated with `@Service` *and* extend `AbstractService`, which provides the interface for
the `initialized()` method.

An (extremely contrived) example:
```typescript
import { Injectable } from '@angular/core';
import { Logger, Service, AbstractService } from '@ubiquits/core/common';
import { lookup } from 'dns';

@Injectable()
@Service()
export class DnsService extends AbstractService {

protected logger: Logger;

constructor(loggerBase: Logger) {
super();
this.logger = loggerBase.source('DNS Service');
}

public lookup(host: string): Promise<string> {
return new Promise((resolve, reject) => {
lookup(host, (err, address) => {
if (err) {
return reject(err);
}
return resolve(address);
})
});
}

public initialize(): Promise<this> | this {
return this.lookup('google.com')
.then((address) => {
this.logger.info(`Lookup passed, found address ${address}`);
return this;
})
.catch((e) => {
this.logger.error(`Failed DNS lookup for Google, aborting bootstrap as either there is no internet connection or the world has ended`);
throw e;
});
}

}
```

In general, you shouldn't need services that abort bootstrapping, but sometimes it is useful, especially if it is a service
that you know it's failure will mean unexpected results if the server were to boot successfully.

This bootstrap blocking is specific to the backend, and while services can be defined in the frontend or common sections,
it has no effect on the frontend bootstrapper.

0 comments on commit a542124

Please sign in to comment.