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

feat(api): domain functions handler core #485

Merged
merged 14 commits into from
Mar 4, 2023

Conversation

riccardoperra
Copy link
Owner

@riccardoperra riccardoperra commented Mar 1, 2023

Handlers

Handlers are domain scoped functions that can be used to extract business logic and make it independent by the rest of the application.

They are registered into a global registry map HandlerRegistry which exposes an add method to register a new handler, and a event property to retrieve the registered handlers

Creating new handlers

Handlers can be created through the HandlerBuilder which use the builder pattern under the hood. In order to create the handler correctly you must define the dependencies, the handler name and it's implementation.

Note the way of creating handler is still experimental and may change

type Deps = { 
 repository: UserRepository
}

// 1. Define handler deps, name and implementation
const handler1 = HandlerBuilder
  .withDependencies<Deps>()
  .withName('handlerName1')
  .withImplementation((dependencies, metadata) => (str: string) => {
    return {
      str,
      a1: 1,
    } as const;
  })
  .build();

// 2. Augment DomainHandlerMap module to register your handler in global types
declare module '@api/domain' {
  interface DomainHandlerMap {
    handlerName: typeof handler1;
  }
}

Registering handlers

Once you have created your handlers, you can register all of them through the registerHandlers helper, which accept a list of handlers, the requested dependencies and the registry

const registry = new HandlerRegistry();

const handlers = registerHandlers(
 [handler1],
 { repository: new UserRepository() },
 registry
)

Handlers can be used from both handlers or registry map

handlers.handlerName1('test'); // ✅ Call handler method with given parameters
handlers.handlerName(1); // ❌ Cannot pass arguments with wrong types.

registry.events.handlerName1('test');

Using in Fastify

  1. Define global handler registry plugin
// plugins/handlerRegistry.ts

export default fp(async fastify => {
  fastify.decorate('handlerRegistry', new HandlerRegistry())
})

declare module 'fastify' {
  interface FastifyInstance {
    handlerRegistry: HandlerRegistry;
  }
}
  1. Create your handlers
// todo/handlers/add.ts

export const addHandler = HandlerBuilder
  .withDependencies<Deps>()
  .withName('addTodo')
  .withImplementation(...)
  .build();

declare module '@api/domain' {
  interface DomainHandlerMap {
    addTodo: typeof addTodo;
  }
}

// todo/handlers/get.ts

export const getHandler = HandlerBuilder
  .withDependencies<Deps>()
  .withName('getTodos')
  .withImplementation(...)
  .build();

declare module '@api/domain' {
  interface DomainHandlerMap {
    getTodos: typeof getHandler;
  }
}
  1. Register your handlers in fastify context
// todo/index.ts
import {getHandler} from './handlers/get.ts';
import {addTodo} from './handlers/add.ts';

const handlers = [getHandler, addTodo] as const;

const todoModule: FastifyPluginAsync = (fastify) => {
  const deps = { };
 
  fastify.decorate(
    'todoService', 
    registerHandlers(handlers, deps, fastify.handlerRegistry)
  );
}

@codesandbox
Copy link

codesandbox bot commented Mar 1, 2023

CodeSandbox logoCodeSandbox logo  Open in CodeSandbox Web Editor | VS Code | VS Code Insiders

@changeset-bot
Copy link

changeset-bot bot commented Mar 1, 2023

⚠️ No Changeset found

Latest commit: d6b32e4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 19:42 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 19:42 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 19:42 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 19:42 — with GitHub Actions Inactive
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2023

Deploy preview for codeimage-ui-dev ready!

✅ Preview
https://codeimage-ui-pqscs2zvd-riccardoperra.vercel.app
https://codeimage-ui-pr-485.vercel.app

Built with commit d6b32e4.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2023

Deploy preview for codeimage-highlight-dev ready!

✅ Preview
https://codeimage-highlight-g8f8s7b6w-riccardoperra.vercel.app
https://codeimage-highlight-pr-485.vercel.app

Built with commit d6b32e4.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2023

Deploy preview for codeimage ready!

✅ Preview
https://codeimage-84dqfflr5-riccardoperra.vercel.app
https://codeimage-app-pr-485.vercel.app

Built with commit d6b32e4.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2023

Deploy preview for codeimage-website-dev ready!

✅ Preview
https://codeimage-website-2z5slz8nm-riccardoperra.vercel.app
https://codeimage-website-pr-485.vercel.app

Built with commit d6b32e4.
This pull request is being automatically deployed with vercel-action

@riccardoperra riccardoperra force-pushed the feat/api-domain-functions branch from 2e03849 to 0bf2658 Compare March 1, 2023 20:56
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 20:59 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 20:59 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 20:59 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 20:59 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 21:28 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 21:28 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 21:28 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 21:28 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:06 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:06 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:06 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:06 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:31 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 22:31 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 23:03 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 23:03 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 23:03 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 1, 2023 23:03 — with GitHub Actions Inactive
@riccardoperra riccardoperra force-pushed the feat/api-domain-functions branch from 0206acc to 3ac27fa Compare March 4, 2023 09:42
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 09:46 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 09:46 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 09:46 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 09:46 — with GitHub Actions Inactive
get handlers(): ResolvedDomainHandlerMap<T> {
return Object.fromEntries(
this.#events.entries(),
) as unknown as ResolvedDomainHandlerMap<T>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@riccardoperra what do you think instead cast to unknown if we create a function to get typed entries?
something like this:

declare type Entries<T> = {
  [K in keyof T]: [K, T[K]];
}[keyof T][];

export const entries = <
  T extends Record<string, unknown> | Map<string, unknown>,
>(
  obj: T,
): Entries<T> => Object.entries(obj) as any;

Copy link
Owner Author

@riccardoperra riccardoperra Mar 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this works because the #entries type is Map<string, GenericHandler>, we need to force the cast because it should resolve the type based on "T", assuming that you are registering ALL handlers you are defining.

Thinks of that Registry contains handlers from multiple modules, and you can call the registryHandlers from difference files

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hackpirodev I also noticed that the current implementation doesn't need as unknown, so it's basically the same. we can remove it using the same fromEntries() fn

@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 19:39 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 19:39 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 19:39 — with GitHub Actions Inactive
@riccardoperra riccardoperra temporarily deployed to Preview March 4, 2023 19:39 — with GitHub Actions Inactive
@riccardoperra riccardoperra changed the base branch from main to next March 4, 2023 19:41
@riccardoperra riccardoperra marked this pull request as ready for review March 4, 2023 19:41
@riccardoperra riccardoperra merged commit f533ece into next Mar 4, 2023
@riccardoperra riccardoperra deleted the feat/api-domain-functions branch March 4, 2023 19:41
riccardoperra added a commit that referenced this pull request Apr 27, 2023
* feat(api): handler core
* feat(api): finish impl
* feat(api): handler builder
* feat(api): simplify code
* feat(api): improve type performance
* feat(api): handler core
* feat(app): cleanup code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants