CITYJS / MEDELLÍN / 2024
TEMPORAL / REACT / FULL-STACK
Digital nomad 🎒 | Mentor 👨🏫 | Speaker 🗣️ | Developer 👨💻 | OSS Contributor 🍫 | Creator of @proyecto26 🧚
Founding Full-Stack Engineer 👷
TEMPORAL FOR EVENT-DRIVEN ARCHITECTURES
SET UP
WORKERS
WORKFLOWS
ACTIVITIES
QUERIES AND SIGNALS
THANK GOODNESS I TOOK
THAT CODING WORKSHOP
Getting Started
Workers
Workflows
Activities
Developers spend an inordinate amount of time coding and testing for complexity and inevitable failure
..is an abstraction that opens up an entirely new,
and fundamentally better development model
Temporal allows developers to deliver more business logic, faster. It eliminates complex plumbing code, so that developers can spend more time building features.
“The business was worried about getting the new capability done for Q4, but we did it in 1 day with Temporal.”
“I estimate we saved two engineers by adopting Temporal for our team of seven, which is significant.”
“There's no such thing as 100% reliability. But we're pretty close in terms of the two systems we have in flight with Temporal.”
"Temporal provides deep understanding of what's happening, at given time, for any message, and for every customer."
When failures happen, Temporal recovers processes where they left off or rolls them back correctly. The end result is fewer incidents and less downtime.
Temporal tracks application state for every execution, giving you insight into issues so you can more effectively debug code and understand application performance.
Orders & Bookings
Inventory & Logistics
Payment Gateway
Customer Onboarding
Customer Lifecycle
Subscription Lifecycle
Payment Processing
Risk & Fraud Analysis
Identity Verification
Operations & Data
CI/CD
Infra management
Infras provisioning
AI & ML
AI Inferences
Data pipeline & ETL
Model Training
Platform
Control Plane
Workflows (DSL)
Order management applications ensure successful completion of an order, like an e-commerce order or flight booking, and often span the end-to-end order lifecycle:
- Reusable components
- Composition over inheritance
- Follow the kiss principle
- It's possible to simulate behaviors of class components with hooks
// Try to avoid this pattern if possible.
const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
- Do Hooks cover all use cases for classes? No
function SignInScreen() {
// useState is not the right way for complex state logic
const [userName, setUserName] = React.useState(getName());
const [password, setPassword] = React.useState('');
const [error, setError] = React.useState(null);
const [loading, setLoading] = React.useState(false);
// Using previous state
const onLogin = async () => {
try {
setLoading(true)
await authenticate(userName, password)
...
} catch (error) {
setError(error)
} finally {
setLoading(false)
}
}
// We can't test this code directly
const onFunA = () => ...
// Creating a new function on every render
const onFunB = () => ...
return ...
}
function SignInScreen () {
const [state, dispatch] = useReducer<SignInReducer>(reducer, {
error: null,
loading: false,
userName: '',
...
})
}
import { Reducer } from 'react'
type OptionError = Error | null
interface SignInScreenState {
error: OptionError,
loading?: boolean,
userName?: string,
}
export enum Action {
SetError,
SetLoading,
SetUsername
}
type SignInScreenAction =
| { type: Action.SetError, value: OptionError }
| { type: Action.SetLoading, value: boolean }
| { type: Action.SetUsername, value: string }
type SignInReducer =
Reducer<SignInScreenState, SignInScreenAction>
const reducer = (
state: SignInScreenState,
action: SignInScreenAction
): SignInScreenState => {
switch (action.type) {
case ACTIONS.SET_ERROR:
return {
...state,
error: action.value
}
case ACTIONS.SET_LOADING:
return {
...state,
loading: action.value
}
case ACTIONS.SET_USERNAME:
return {
...state,
userName: action.value
}
...
default:
return state
}
}
more control for
performance
A way to reuse stateful logic 💡
function useAuth() {
// Use stores with React Context API, etc
const [state, dispatch] = useStore();
// memoized function for performance
const onSignIn = useCallback(async (
document: string,
password: string,
) => {
const { data: { access_token: accessToken } } = await axios.post(
authEndpoint,
{ document, password }
);
dispatch({ type: Action.LoadToken, value: accessToken });
}, [dispatch]);
return {
state,
onSignIn
};
}
function useAuth() {
...
const onLoadUser = useCallback(async () => {
const { data: user } = await axios.get(`${userEndpoint}/me`, {
headers: { 'Authorization': `Bearer ${state.token}` }
});
dispatch({
type: Action.LoadUser,
value: user
});
}, [state.token, dispatch]);
return {
...,
onLoadUser
}
}
Only changes if one of the dependencies has changed 💡
- Single source of truth
- No code duplication
- All actions are visible
- No more need the deep component tree nesting reusing state logic (HOCs can be used for specific cases like adding behaviors, injecting stores via props, etc)
- Many components can react to same action
- Test components and hooks separately
import { act, renderHook } from '@testing-library/react-hooks'
import { useDictionary } from '..'
describe('useDictionary hook', () => {
it('should handle onUpdateValue action', () => {
const initialState = {
boolean: false,
}
const { result } = renderHook(() => useDictionary(initialState))
act(() => {
result.current.onUpdateValue('boolean', true)
})
expect(result.current.state.boolean).toBeTruthy()
})
})
Encourage good testing practices 💡
it('should handle useEffect hook', async () => {
const spyInitialize = jest.spyOn(HubConnection, 'initialize').mockResolvedValueOnce()
const spyClose = jest.spyOn(HubConnection, 'close').mockReturnValueOnce()
const { result, waitForNextUpdate, unmount } = renderHook(() => useMyHook())
await waitForNextUpdate()
expect(spyInitialize).toHaveBeenCalled()
expect(result.current.initialized).toBeTruthy()
unmount()
expect(spyClose).toHaveBeenCalled()
})
Just use the hook directly and assert the results 💡
import { useState, useEffect, useCallback } from 'react'
import io, { Socket } from 'socket.io-client'
import { WS_DOMAIN } from '../../contants'
export function useSocket () {
const [socket, setSocket] = useState<typeof Socket | null>(null)
useEffect(function () {
const newSocket = io.connect(WS_DOMAIN)
setSocket(newSocket)
return () => {
newSocket.removeAllListeners()
newSocket.close()
}
}, [])
const onSendMessage = useCallback((eventName: string, data: any) => {
if(socket) socket.emit(eventName, data)
}, [socket])
return {
socket,
onSendMessage
}
}
import { useState, useEffect, useCallback } from 'react'
import io, { Socket } from 'socket.io-client'
import { WS_DOMAIN } from '../../contants'
import { useAuth } from '../useAuth'
export function useSocketWithAuth () {
const { state } = useAuth()
const [socket, setSocket] = useState<typeof Socket | null>(null)
useEffect(function () {
if (!state.token) return
const newSocket = io.connect(WS_DOMAIN, {
query: { token: state.token }
})
setSocket(newSocket)
return () => {
newSocket.removeAllListeners()
newSocket.close()
}
}, [state.token])
return {
socket
}
}
DEMO / REACT / REMIXJS
TEMPORAL / NODEJS
NESTJS / NX / 🍭🍬🍫🍦
Of course Elon, Code here! 🎁
Juan D. Nicholls
jd@baxus.co