Logo

dev-resources.site

for different kinds of informations.

Serverless dependency management in OpenJS Architect

Published at
3/8/2021
Categories
architect
aws
serverless
cloudnative
Author
pchinjr
Author
7 person written this
pchinjr
open
Serverless dependency management in OpenJS Architect

OpenJS Architect is an open-source framework for writing and deploying serverless applications to AWS. Each Architect project is comprised of many small and independent Lambda functions that are executed statelessly in their own isolated runtimes.

So how do we manage shared code and dependencies between functions?

Dependencies defined in your root package.json

You are likely to use some dependencies in each Lambda function that you are deploying like including the @architect/functions library. Architect has the ability to discover use your project's root package.json to install dependencies into your Lambda functions. Let's take a look at an example.

First, start by creating a new Architect project

npm init @architect ./arc-example
cd arc-example
npm install @architect/functions
Enter fullscreen mode Exit fullscreen mode

By installing @architect/functions to the root package.json it will be available to all your functions when you require it. We won't need to manage dependencies with a per-function package.json.

So now we can use it in our get-index function.

// src/http/get-index/index.js

let arc = require('@architect/functions')

async function route(req) {
  return {
    statusCode: 200,
    html: `<h1> Praise Cage </h1>`
  }
}

exports.handler = arc.http.async(route)
Enter fullscreen mode Exit fullscreen mode

src/shared and src/views

Another way we can handle shared code is with a special folder src/shared. Architect provides an easy way to abstract and reuse code in your functions. Most applications need to share logic, templates, or utilities. In order to do this, Architect uses a folder convention to copy the contents of src/shared and src/views into each function's node_modules directory.

It's important to note that the entire contents of src/shared are copied recursively. I suggest you keep the directory structure as flat as possible, and the payloads as small as possible to improve performance.

Let's create an example helper function.

src/shared example

To get started, create a new project from the command line.

npm init @architect ./arc-shared-views
Enter fullscreen mode Exit fullscreen mode

Next, we can modify the app.arc file in the root of the project with the following:

# app.arc file
@app 
arc-shared

@http
get /
get /answer
Enter fullscreen mode Exit fullscreen mode

Now we can start to build out our src/shared modules by creating a new folder at src/shared/helper.js. In this example, we need to make sure a number is converted to a string and this helper function will do the trick!

// src/shared/helper.js

function theAnswer() {
  //really important number that needs to be converted to a string
  let universe = 42 
  return universe.toString()
}

module.exports = theAnswer
Enter fullscreen mode Exit fullscreen mode

We can use this helper in all of our functions by just requiring it from @architect/shared. Modify the get-answer function with the following:

// src/http/get-answer/index.js

let Answer = require('@architect/shared/helper')

exports.handler = async function http (req) {
  return {
    statusCode: 200,
    headers: {
      'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0',
      'content-type': 'text/html; charset=utf8'
    },
    body: `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title> The Answer is </title>
      </head>
      <body>
        <p> This is the Answer: ${Answer()} </p>
      </body>
      </html>
    `
  }
}
Enter fullscreen mode Exit fullscreen mode

Run npm start from the command line and take a look at our code structure. Sandbox will hydrate our functions with a node_modules/@architect/shared directory which is part of the function's payload when deployed and executed.

.
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ http
β”‚   β”‚   β”œβ”€β”€ get-index/
β”‚   β”‚   └── get-answer/
β”‚   β”‚
β”‚   └── shared/
β”‚       └── helper.js
β”‚   
β”œβ”€β”€ app.arc
└── package.json
Enter fullscreen mode Exit fullscreen mode

When you navigate to http://localhost:3333/answer you will be greeted with data from our shared module, and it can be used by any other function.

src/views example

The src/views folder is a special location that allows you to include code for each of your HTPP functions with a GET route. Continuing with our previous src/shared example we will include a layout template that your HTTP functions can use.

Modify the app.arc file to match the following:

@app
arc-shared

@http
get /
get /answer 
get /about
get /css/:stylesheet

@views
get / 
get /about
Enter fullscreen mode Exit fullscreen mode

What we've done is added two new routes - /about and css/:stylesheet, then declared that two of the routes / and /about should receive a copy of the modules in src/views.

Create a new folder and file, src/views/layout.js. In this file we'll write the following contents:

module.exports = function Layout (props) {
  props = props || {}
  let heading = props.heading || 'Architect views!'
  return `
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Architect example</title>
 <link rel="stylesheet" href="/css/styles.css">
</head>
<body>
 <h1>${heading}</h1>
</body>
</html>
`
}
Enter fullscreen mode Exit fullscreen mode

This is our shared view template that will be used by each GET route listed under the @views pragma in the app.arc file.

Next, we'll modify src/http/get-index/index.js with the following:

let Layout = require('@architect/views/layout')

exports.handler = async function http (request) {
  try {
    return {
      statusCode: 200,
      headers: {
        'content-type':'text/html; charset=utf8'
      }, 
      body: Layout()
    }
  } catch (e) {
    console.error(e)
    return {
      headers: {
        type: 'application/json; charset=utf8',
      },
      status: 500,
      body: JSON.stringify({
        name: e.name,
        message: e.message,
        stack: e.stack
      }, null, 2)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This function will call the layout file and return its output as the body of the response. Next, we can set up the about page to send a different set of props to the layout. Modify sec/http/get-about/index.js with the following:

let Layout = require('@architect/views/layout')

exports.handler = async function http (request) {
  try {
    return {
      statusCode: 200,
      headers: {
        'content-type':'text/html; charset=utf8'
      }, 
      body: Layout({heading: 'About'})
    }
  } catch (e) {
    console.error(e)
    return {
      status: 500,
      type: 'application/json; charset=utf8',
      body: JSON.stringify({
        name: e.name,
        message: e.message,
        stack: e.stack
      }, null, 2)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now when /about is requested, this function will execute and be able to return the data being passed into Layout().

Finally, we have some finder control over which GET functions will have /src/views copied into it. We do this with the @views pragma in the app.arc file. We want to create a URL to our style sheet, but this function doesn't need access to the layout code. Only the GET routes under @views will have the src/views code copied into it. Our first route of /answer won't have src/views modules copied into node_modules.

Modify the code in src/http/get-css-000stylesheet/index.js with the following:

const styles = `
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
`
exports.handler = async function http (request) {
  return {
    statusCode: 200,
    type: 'text/css; charset=utf8',
    body: styles
  }
}
Enter fullscreen mode Exit fullscreen mode

OK! Go ahead and run npm start from the project root and navigate to http://localhost:3333 to see our app in action. Change the route to http://localhost:3333/about and you'll see that our proper were passed as expected.

See full example code in the repo: https://github.com/pchinjr/arc-shared-views-example

architect Article's
30 articles in total
Favicon
Top Architect and Structural Engineer for Custom Designs
Favicon
Similarities of a startup and museum of art. The role of an architect.
Favicon
Exploring the Intersection of CAD Designing and Artificial Intelligence
Favicon
Enhancing Negotiation and Presentation Skills for Enterprise Architects
Favicon
Software Architect
Favicon
The Power of Practicality: How Hands-On Coding Enhances Architectural Decision-Making
Favicon
Making the leap to AWS with Architect
Favicon
Interview with Solutions Architect
Favicon
Que es ser Arquitecto de soluciones y por que certificarse como AWS Solutions Architect?
Favicon
Introduction to Enterprise Architecture (EA)
Favicon
Tales from the Real World - Architecting the Transformation
Favicon
Creating successful high-level software solutions
Favicon
Microsoft Azure Architect Technologies AZ 303 Introduction
Favicon
A First Look at Architect
Favicon
AWS IAM User
Favicon
Serverless dependency management in OpenJS Architect
Favicon
Serverless OAuth com MΓΊltiplos Provedores
Favicon
AZ-303 Azure Architect Technologies - Introduction
Favicon
AZ-303 Azure Active Directory [Module 1]
Favicon
Serverless GitHub OAuth with OpenJS Architect and Begin - Part 2
Favicon
Strangler except serverless with OpenJS Architect
Favicon
Express style API with OpenJS Architect
Favicon
Using Tailwind with Begin.com and Architect
Favicon
Connect an Architect App to a Cloudflare Domain
Favicon
Top 10 takeaways from "How to become an aspiring architect" meetup
Favicon
AWS Solutions Architect Associate
Favicon
SNS vs SQS? AWS Messaging Services - Know the difference
Favicon
How not to architect your project?Β 
Favicon
Thoughts on Software Architecture
Favicon
5 things I wish I knew before using CloudFormation to build a CodePipeline for Lambda Functions

Featured ones: