Fan-out (one-to-many)
The fan-out pattern enables you to send a single event and trigger multiple functions in parallel (one-to-many). The key benefits of this approach are:
- Reliability: Logic from each function runs independently, meaning an issue with one function will not affect the other(s).
- Performance: As functions area run in parallel, all of the work will execute faster than running in sequence.
A use case for fan-out is, for example, when a user signs up for your product. In this scenario, you may want to:
- Send a welcome email
- Start a trial in Stripe
- Add the user to your CRM
- Add the user's email to your mailing list
The fan-out pattern is also useful in distributed systems where a single event is consumed by functions running in different applications.
How to fan-out to multiple functions
Since Inngest is powered by events, implementing fan-out is as straightforward as defining multiple functions that use the same event trigger. Let's take the above example of user signup and implement it in Inngest.
First, set up a /signup
route handler to send an event to Inngest when a user signs up:
app/routes/signup/route.ts
import { inngest } from '../inngest/client';
export async function POST(request: Request) {
// NOTE - this code is simplified for the of the example:
const { email, password } = await request.json();
const user = await createUser({ email, password });
await createSession(user.id);
// Send an event to Inngest
await inngest.send({
name: 'app/user.signup',
data: {
user: {
id: user.id,
email: user.email,
},
},
});
redirect('https://myapp.com/dashboard');
}
Now, with this event, any function using "app/user.signup"
as its event trigger will be automatically invoked.
Next, define two functions: sendWelcomeEmail
and startStripeTrial
. As you can see below, both functions use the same event trigger, but perform different work.
inngest/functions.ts
const sendWelcomeEmail = inngest.createFunction(
{ id: 'send-welcome-email' },
{ event: 'app/user.signup' },
async ({ event, step }) => {
await step.run('send-email', async () => {
await sendEmail({ email: event.data.user.email, template: 'welcome');
});
}
)
const startStripeTrial = inngest.createFunction(
{ id: 'start-stripe-trial' },
{ event: 'app/user.signup' },
async ({ event }) => {
const customer = await step.run('create-customer', async () => {
return await stripe.customers.create({ email: event.data.user.email });
});
await step.run('create-subscription', async () => {
return await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_1MowQULkdIwHu7ixraBm864M' }],
trial_period_days: 14,
});
});
}
)
You've now successfully implemented fan-out in our application. Each function will run independently and in parallel. If one function fails, the others will not be disrupted.
Other benefits of fan-out include:
- Bulk Replay: If a third-party API goes down for a period of time (for example, your email provider), you can use Replay to selectively re-run all functions that failed, without having to re-run all sign-up flow functions.
- Testing: Each function can be tested in isolation, without having to run the entire sign-up flow.
- New features or refactors: As each function is independent, you can add new functions or refactor existing ones without having to edit unrelated code.
- Trigger functions in different codebases: If you have multiple codebases, even using different programming languages (for example Python or Go), you can trigger functions in both codebases from a single event.