-
Notifications
You must be signed in to change notification settings - Fork 0
feat: first example with services #1
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import type { Repository } from '@nextorders/queue' | ||
| import { Entity } from '@nextorders/queue' | ||
| import { Events } from '../types' | ||
|
|
||
| export class Email extends Entity { | ||
| constructor(repository: Repository) { | ||
| super({ | ||
| name: 'email', | ||
| eventsToConsume: [ | ||
| Events.UserCreated, | ||
| ], | ||
| repository, | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import type { Repository } from '@nextorders/queue' | ||
| import { Entity } from '@nextorders/queue' | ||
|
|
||
| export class User extends Entity { | ||
| constructor(repository: Repository) { | ||
| super({ | ||
| name: 'user', | ||
| eventsToConsume: [], | ||
| repository, | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import type { EventMessage } from './types' | ||
| import { Repository } from '@nextorders/queue' | ||
| import { Email } from './entities/email' | ||
| import { User } from './entities/user' | ||
|
|
||
| class QueueRepository extends Repository { | ||
| user = new User(this) | ||
| email = new Email(this) | ||
|
|
||
| async publish<T extends EventMessage>(event: T['event'], data: T['data']) { | ||
| return super.publish(event, data) | ||
| } | ||
| } | ||
|
|
||
| export const repository = new QueueRepository() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { BaseEventMessage, Status } from '@nextorders/queue' | ||
|
|
||
| // All possible events | ||
| export enum Events { | ||
| UserCreated = 'userCreated', | ||
| EmailSent = 'emailSent', | ||
| } | ||
|
|
||
| export type EventMessage = UserCreated | EmailSent | ||
|
|
||
| export type EventHandler = (msg: EventMessage) => Promise<Status> | ||
| export type EventMessageHandler<T = EventMessage['data']> = (data: T) => Promise<boolean> | ||
|
|
||
| export type EventHandlerMap = Record<EventMessage['event'], EventMessageHandler> | ||
|
|
||
| export interface UserCreated extends BaseEventMessage { | ||
| event: typeof Events.UserCreated | ||
| data: { | ||
| id: string | ||
| name: string | ||
| email: string | ||
| } | ||
| } | ||
|
|
||
| export interface EmailSent extends BaseEventMessage { | ||
| event: typeof Events.EmailSent | ||
| data: { | ||
| email: string | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { repository } from '../repository' | ||
| import { Events } from '../repository/types' | ||
|
|
||
| // Service 1: User Service | ||
| const body = { | ||
| name: 'John Doe', | ||
| email: '5Tt9o@example.com', | ||
| } | ||
|
|
||
| // DB: Save user in database | ||
| const newUser = { | ||
| id: '123', | ||
| ...body, | ||
| } | ||
|
|
||
| // Publish Event for other services | ||
| repository.publish(Events.UserCreated, { | ||
| id: newUser.id, | ||
| name: newUser.name, | ||
| email: newUser.email, | ||
| }) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||||||||||||||||||||||||||
| // On service start | ||||||||||||||||||||||||||||||
| import { repository } from '../repository' | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Connect to RabbitMQ | ||||||||||||||||||||||||||||||
| repository.connect('amqp://guest:guest@localhost:5672') | ||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Await the connection; use environment variables for credentials. Critical issues:
Apply this diff: -// On service start
-import { repository } from '../repository'
-
-// Connect to RabbitMQ
-repository.connect('amqp://guest:guest@localhost:5672')
+// On service start
+import { repository } from '../repository'
+
+// Connect to RabbitMQ
+const rabbitUrl = process.env.RABBIT_URL ?? 'amqp://guest:guest@localhost:5672'
+await repository.connect(rabbitUrl).catch((error) => {
+ console.error('Failed to connect to RabbitMQ:', error)
+ process.exit(1)
+})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { EventHandlerMap, UserCreated } from '../repository/types' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { repository } from '../repository' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Events } from '../repository/types' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Service 2: Email Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Consume to Events | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repository.consume(repository.email.name, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [Events.UserCreated]: handleUserCreated, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } as EventHandlerMap) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Business logic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function handleUserCreated(data: UserCreated['data']): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Service logic: Send email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await sendEmail(data.email) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Failed to send email:', error) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function sendEmail(email: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn('Sending email to', email) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Publish Event for other services | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await repository.publish(Events.EmailSent, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connect first; await consume/publish; await sendEmail in handler
Apply: -import type { EventHandlerMap, UserCreated } from '../repository/types'
+import type { EventHandlerMap, UserCreated } from '../repository/types'
import { repository } from '../repository'
import { Events } from '../repository/types'
// Service 2: Email Service
-
-// Consume to Events
-repository.consume(repository.email.name, {
- userCreated: handleUserCreated,
-} as EventHandlerMap)
+// Connect and start consumer
+await repository.connect(process.env.RABBIT_URL ?? 'amqp://guest:guest@localhost:5672')
+await repository.consume(repository.email.name, {
+ userCreated: handleUserCreated,
+} as EventHandlerMap)
@@
async function handleUserCreated(data: UserCreated['data']): Promise<boolean> {
try {
// Service logic: Send email
- sendEmail(data.email)
+ await sendEmail(data.email)
return true
} catch (error) {
console.error('Failed to send email:', error)
return false
}
}
@@
async function sendEmail(email: string) {
console.warn('Sending email to', email)
// Publish Event for other services
- repository.publish(Events.EmailSent, {
+ await repository.publish(Events.EmailSent, {
email,
})
}If you adopt Partial handler maps in the repository (see other comment), remove the cast: { userCreated: handleUserCreated }. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // On service start | ||
| import { repository } from '../repository' | ||
|
|
||
| // Connect to RabbitMQ | ||
| repository.connect('amqp://guest:guest@localhost:5672') | ||
|
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Await the connection; use environment variables; eliminate duplication. This file is identical to
Additionally, consider extracting this shared startup logic to a common utility function to eliminate duplication. Apply the same fix as in service1/service-start.ts: -// On service start
-import { repository } from '../repository'
-
-// Connect to RabbitMQ
-repository.connect('amqp://guest:guest@localhost:5672')
+// On service start
+import { repository } from '../repository'
+
+// Connect to RabbitMQ
+const rabbitUrl = process.env.RABBIT_URL ?? 'amqp://guest:guest@localhost:5672'
+await repository.connect(rabbitUrl).catch((error) => {
+ console.error('Failed to connect to RabbitMQ:', error)
+ process.exit(1)
+})Then, consider creating a shared export async function startService(repository: QueueRepository): Promise<void> {
const rabbitUrl = process.env.RABBIT_URL ?? 'amqp://guest:guest@localhost:5672'
await repository.connect(rabbitUrl).catch((error) => {
console.error('Failed to connect to RabbitMQ:', error)
process.exit(1)
})
}🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Connect before publish and await the Promise
Currently publish is fired without a connection and not awaited. This will throw (“Connection is not created”) and/or drop errors.
Apply:
If top-level await isn’t allowed, wrap in an async main and call main().catch(console.error).
🤖 Prompt for AI Agents