[Serverless] Refactoring: Using Ports and Adapters pattern to refactor code
The original code:
createGroup.ts:
import { v4 as uuidv4 } from "uuid"; import "source-map-support/register"; import * as AWS from "aws-sdk"; import { APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyResult, } from "aws-lambda"; import { getUserId } from "../../auth/utils"; const docClient = new AWS.DynamoDB.DocumentClient(); const groupTables = process.env.GROUPS_TABLE; export const handler: APIGatewayProxyHandler = async ( event: APIGatewayProxyEvent ): Promise<APIGatewayProxyResult> => { console.log("Processing event: ", event); const itemId = uuidv4(); const parsedBody = JSON.parse(event.body); const authorization = event.headers.Authorization; const splits = authorization.split(" "); const jwtToken = splits[1]; const newItem = { id: itemId, userId: getUserId(jwtToken), ...parsedBody, }; await docClient .put({ TableName: groupTables, Item: newItem, }) .promise(); return { statusCode: 201, headers: { "Access-Control-Allow-Origin": "*", }, body: JSON.stringify({ newItem, }), }; };
getGroups.ts:
import * as AWS from "aws-sdk"; import { APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyResult, } from "aws-lambda"; const docClient = new AWS.DynamoDB.DocumentClient(); const groupTables = process.env.GROUPS_TABLE; export const handler: APIGatewayProxyHandler = async ( event: APIGatewayProxyEvent ): Promise<APIGatewayProxyResult> => { console.log("Processing event: ", event); const result = await docClient .scan({ TableName: groupTables, }) .promise(); const items = result.Items; return { statusCode: 200, headers: { "Access-Control-Allow-Origin": "*", }, body: JSON.stringify({ items, }), }; };
Tow entry functions both contains busniess logic code and data access code (dynamoDB).
What we want is:
Entry function --> Busniess logic code --> Data access layer
First, create interfaces for both Reqest & Model:
requests/CreateGroupRequest.ts:
export interface CreateGroupRequest { name: string; description: string; }
models/Group.ts:
export interface Group { id: string; name: string; description: string; userId: string; }
Then create data access layer code, it mainly handle all the dynamoDB operations.
dataAccess/groupAccess.ts:
import * as AWS from "aws-sdk"; import { DocumentClient } from "aws-sdk/clients/dynamodb"; import {Group} from '../models/Group' export class GroupAccess { constructor( private readonly docClient: DocumentClient = new AWS.DynamoDB.DocumentClient(), private readonly groupsTable = process.env.GROUPS_TABLE ) { } async getAllGroups(): Promise<Group[]> { console.log('Getting all groups'); const result = await this.docClient.scan({ TableName: this.groupsTable, }).promise() const items = result.Items; return items as Group[]; } async createGroup(group: Group): Promise<Group> { console.log('Creating a group with id', group.id) await this.docClient.put({ TableName: this.groupsTable, Item: group }).promise(); return group; } }
Then create busniessLogic layer, it mainly talk to data access layer and passing and returning the data.
busniessLogic/groups.ts:
import * as uuid from "uuid"; import { Group } from "../models/Group"; import { GroupAccess } from "../dataAccess/groupAccess"; import { CreateGroupRequest } from "../requests/CreateGroupRequest"; import { getUserId } from "../auth/utils"; const groupAccess = new GroupAccess(); export async function getAllGroups(): Promise<Group[]> { return groupAccess.getAllGroups(); } export async function createGroup( createGroupRequest: CreateGroupRequest, jwtToken: string ) { const itemId = uuid.v4(); const userId = getUserId(jwtToken); return await groupAccess.createGroup({ id: itemId, userId: userId, name: createGroupRequest.name, description: createGroupRequest.description, }); }
Lastly, entry function will just talk to busniess layer code:
createGroup.ts:
import "source-map-support/register"; import { APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyResult, } from "aws-lambda"; import { createGroup } from "../../busniessLogic/groups"; import { CreateGroupRequest } from "../../requests/CreateGroupRequest"; export const handler: APIGatewayProxyHandler = async ( event: APIGatewayProxyEvent ): Promise<APIGatewayProxyResult> => { console.log("Processing event: ", event); const newGroup: CreateGroupRequest = JSON.parse(event.body); const authorization = event.headers.Authorization; const splits = authorization.split(" "); const jwtToken = splits[1]; const newItem = await createGroup(newGroup, jwtToken); return { statusCode: 201, headers: { "Access-Control-Allow-Origin": "*", }, body: JSON.stringify({ newItem, }), }; };
getGroups.ts:
import { APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyResult, } from "aws-lambda"; import { getAllGroups } from "src/busniessLogic/groups"; export const handler: APIGatewayProxyHandler = async ( event: APIGatewayProxyEvent ): Promise<APIGatewayProxyResult> => { console.log("Processing event: ", event); const items = await getAllGroups(); return { statusCode: 200, headers: { "Access-Control-Allow-Origin": "*", }, body: JSON.stringify({ items, }), }; };