# How adapters work

{% hint style="warning" %}
This project and documentation are work in progress
{% endhint %}

{% hint style="info" %}
Example code: [src/adapters](https://github.com/jbreckmckye/node-typescript-architecture/tree/master/src/adapters)
{% endhint %}

Adapters are functions that do two things:

* they create part of the **Context** used by operations
* they can wrap operations in their own functions (optional)

In the case of the [PostgreSQL adapter](https://github.com/jbreckmckye/node-typescript-architecture/tree/master/src/adapters/postgres), we do both, creating a `ctx` with database functions, and an `op` that wraps a given operation in a transaction:

```typescript
export async function $adapter (): Promise<ContextAdapter> { 
  const pool = createConnectionPool()

  return async function adapter <I, O> (op: Operation<I, O>) {

    const client = await pool.connect()

    return {
      op: (ctx, input: I) => wrapTransaction(client, () => op(ctx, input)),
      ctx: {
        backend: {
          bookStore: {
            add:          withClient(client, Repository.Book.add),
            find:         withClient(client, Repository.Book.find)
          },

          ...etc
        }
      }
    }
  }
}
```

Let's walk through this code sample:

* The adapter is wrapped in `$adapter` - in this codebase a dollar signifies a partially applied / higher order function. So `$adapter()` returns the function `adapter`.
* In the closure of the partially applied function, we create a database connection pool. This will be shared by all invocations of `adapter`.
* The `adapter` is called with an `op` (operation)
  * Note that if the adapter needed runtime parameters, they would be the second argument
* The adapter then awaits on a database connection
* We return a record with two fields:
  * an `op` that wraps the provided operation in a database transaction;
  * a `ctx` that exposes functions which ultimately run SQL statements

You'll notice that the context functions are themselves the product of another function called `withClient`. We'll talk about this further down the page.

### Sharing resources between adapter calls

The reason we use a partially applied function called `$adapter` is because that provides the perfect environment for us to share things between adapter calls. Remember that the `adapter` is called **every time a user request comes in**. The more work you can do in the outer closure, the better.

{% hint style="info" %}

#### A note on performance

You might wonder about the performance of re-creating the context on every request. Doesn't recreating all those objects and functions create loads of overhead? In practice, however, V8 is pretty smart about reusing things like anonymous functions and structs with a stable shape (provided they come from the same site in the source text).

Node optimisations can mean that certain performance 'tricks' have unexpected effects. For instance, you might be forgiven for assuming that creating a single function and re-calling  `Function.prototype.bind` would be much more efficient than re-constructing the whole anonymous function. Surely, you'd think, doesn't it save a load of allocation? Actually, usually the latter is more performant, both from a runtime and a GC perspective, because it's an optimisation path V8 recognises and can inline away.

For this reason, be sure to accompany any performance tuning you do with actual tests.
{% endhint %}

### How are adapters written?

Ultimately it's up to you how you want to structure your adapters, but NTA does offer a suggestion:

* write 'core functions' that have dependencies injected, just like operation functions
* write 'wrapper functions' that can inject those dependencies into the core methods
* use a folder structure that matches the structure of your adapter context object

For instance, an adapter 'core function' might look like this one, taken from the [MemoryDB adapter ](https://github.com/jbreckmckye/node-typescript-architecture/tree/master/src/adapters/memoryDB)in the example project:

```typescript
// Repository.book.find
// Finds a book
export async function find (db: MemoryDB, input: UUID): Promise<Book|null> {
  for (const book of db.books) {
    if (book.id === input) {
      return castBook(book)
    }
  }
  return null
}
```

Just like with an operation, we don't directly depend on our storage concern. Instead findBook has it injected at a higher level of the adapter. To provide it we create a `withDB` function:

```typescript
export function withDB <I, O> (db: MemoryDB, fn: (db: MemoryDB, input: I) => Promise<O>): (input: I) => Promise<O> {
  return function (input: I) {
    return fn(db, input)
  }
}
```

What is a `MemoryDB`? It's just a type defined inside the adapter folder itself

```typescript
export type MemoryDB = {
  books: Set<Book>,
  loans: Set<Loan>,
  users: Set<User>,
}
```

The advantage of this is that it makes `findBook` *very* easy to test:

```typescript
describe('findBook', () => {
  it('Returns a book of the correct ID', () => {
    const db = { books: new Set() }
    const book = { id: 'the_book' }
    db.books.add(book)
    
    const result = findBook(db, 'the_book')
    expect(result).toBe(book)
  })
})
```

Then in our adapter code, we can use `withDB` to do the dependency injection:

```typescript
export async function $adapter (): Promise<Ctx.ContextAdapter> {
  const db = createDB()
  const backend = {
    bookStore: {
      add:          withDB(db, Repository.Book.add),
      find:         withDB(db, Repository.Book.find)
    },

    loanStore: {
      takeLoan:     withDB(db, Repository.Loan.takeLoan),
      endLoan:      withDB(db, Repository.Loan.endLoan),
      getUserLoans: withDB(db, Repository.Loan.getUserLoans),
      getLoan:      withDB(db, Repository.Loan.getLoan)
    },

    userStore: {
      add:          withDB(db, Repository.User.add),
      remove:       withDB(db, Repository.User.remove),
      find:         withDB(db, Repository.User.find)
    }
  }

  // etc.
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jbreckmckye.gitbook.io/node-ts-architecture/creating-adapters/how-adapters-work.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
