Skip to content

Commit

Permalink
Merge branch 'brefphp:master' into feature/support-ap-southeast-3
Browse files Browse the repository at this point in the history
  • Loading branch information
kevincerro authored Jun 28, 2024
2 parents 5da1cf5 + ddf2398 commit 8a0fb65
Show file tree
Hide file tree
Showing 88 changed files with 4,725 additions and 2,809 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
node-version: 18
# serverless is required by some tests
- name: Install serverless
run: npm i -g serverless
run: npm i -g serverless@3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ layers.json:
test-stack:
serverless deploy -c tests/serverless.tests.yml

preview:
cd website && make preview

.PHONY: demo layers.json test-stack
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
---
layout: home
---
[![Running PHP made simple. Bref provides tools and documentation to easily deploy and run serverless PHP applications. Learn more](website/public/social-card.png)](https://bref.sh/)

[![Running PHP made simple. Bref provides tools and documentation to easily deploy and run serverless PHP applications. Learn more](docs/readme-screenshot.jpg)](https://bref.sh/)

[![Build Status](https://travis-ci.com/brefphp/bref.svg?branch=master)](https://travis-ci.com/brefphp/bref)
[![Latest Version](https://img.shields.io/github/release/brefphp/bref.svg?style=flat-square)](https://packagist.org/packages/bref/bref)
[![Monthly Downloads](https://img.shields.io/packagist/dm/bref/bref.svg)](https://packagist.org/packages/bref/bref/stats)

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"psr/container": "^1.0|^2.0",
"psr/http-message": "^1.0|^2.0",
"psr/http-server-handler": "^1.0",
"riverline/multipart-parser": "^2.0.6",
"riverline/multipart-parser": "^2.1.2",
"symfony/process": "^4.4|^5.0|^6.0|^7.0"
},
"require-dev": {
Expand Down
6 changes: 5 additions & 1 deletion docs/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@
"href": "https://serverless-visually-explained.com/?ref=bref-menu"
},
"community": "",
"case-studies": ""
"case-studies": "",
"sentry": {
"title": "Sentry integration",
"href": "/sentry"
}
}
19 changes: 13 additions & 6 deletions docs/case-studies.md → docs/case-studies.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
---
introduction: A collection of case studies of serverless PHP applications built using Bref. Learn about performance, costs and migrations from existing projects.
---
import { NextSeo } from 'next-seo';

<NextSeo description="A collection of case studies of serverless PHP applications built using Bref. Learn about performance, costs and migrations from existing projects." />

# Case studies

This page collects case studies of serverless PHP applications built with or migrated to Bref.

These help learn for real use cases about costs, performance and migration efforts.

## Websites
## Applications

- [Treezor](./case-studies/treezor.mdx)

How Treezor, a banking platform, went from legacy code on servers to a serverless architecture with Bref.

- [Spreaker](https://careers.spreaker.com/engineering/rebuilding-spreaker-web-listening-experience-with-php-and-serverless/)

How Spreaker, a podcast hosting platform, rewrote a decade-old monolith with Bref, Laravel Octane and Livewire.

- [externals.io](https://mnapoli.fr/serverless-case-study-externals/)

Expand All @@ -34,8 +42,7 @@ These help learn for real use cases about costs, performance and migration effor

- [PDF reporting generation](https://devops-life.com/blog/2020/03/06/how-serverless-saved-us-for-$2-with-bref-sh/)

Feedback to understand the benefits of going serverless with a real use case (PDF generation);
They generated 2,000 PDFs in <2 min for 2$ using Symfony applications shipped inside lambdas using Bref.sh
A case study of going serverless for PDF generation. They generated 2,000 PDFs in less than 2 min for $2 using Symfony and Bref.

## Others

Expand Down
107 changes: 107 additions & 0 deletions docs/case-studies/treezor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { NextSeo } from 'next-seo';
import Image from 'next/image';
import illustration from './treezor/treezor-illustration.jpeg';

<NextSeo
title="Bref case study: How Treezor runs a serverless banking platform with Bref"
description="Treezor migrated a legacy PHP application from servers to serverless on AWS Lambda. They gained increased scalability, faster response times and fewer production incidents." />

# Treezor: a serverless banking platform

<div className="mt-6 text-gray-500 text-lg">
This case study dives into how Treezor went serverless for their banking platform.
From legacy code running on servers to a serverless monolith, and then event-driven microservices on AWS with Bref.
</div>

<Image className="mt-6 rounded-lg" src={illustration} />

[Treezor](https://www.treezor.com/) is a banking-as-a-service platform that serves millions of transactions every day. **You might be using it every day** through its clients: neobanks, employee benefit cards, company travel cards, and many other financial services.

Because Treezor's clients have very different use cases, the platform's infrastructure must be able **to scale and be resilient** to accommodate various usage patterns. Whether it's a luncheon voucher transaction spike at lunchtime or a monthly batch of transactions by corporate clients. On top of that, some API endpoints need to respond fast, **in near real-time**, for example to authorize live credit card payments.

To build such a platform, Treezor migrated from a legacy PHP application running on servers to a serverless architecture running on AWS Lambda with Bref. They did such a migration in 3 steps:

- First, they validated the serverless infrastructure by building a new service as a serverless PHP application.
- Then, they did a "lift-and-shift" migration by **running the legacy PHP application on AWS Lambda**.
- Finally, they slowly refactored the legacy application into multiple **PHP microservices** using the "strangler" pattern.

Let's explore this serverless migration in more detail.

## The original legacy stack

The original stack was a legacy PHP monolith, built with no framework, running on [OVH](https://ovh.com/) servers.

That monolith was responsible for the "Core Banking" API, i.e. handling all the critical operations of the system, like authorizing credit card payments or executing bank transfers.

Because it was running on servers, handling unpredictable traffic spikes was challenging.

## Validating the serverless infrastructure

Migrating a Core Banking system to a new infrastructure is not something you do every day. To make sure that AWS and [AWS Lambda](https://aws.amazon.com/lambda/) were a good fit, the team first wanted to validate the stack.

They did so by **building a new service entirely serverless** (greenfield project). They built it using PHP, Lumen, Bref, AWS Lambda, SQS, DynamoDB, SNS, S3, and KMS.

That new service was the API exposed to Treezor's client to manage day-to-day bank operations. It was deployed to production at the end of 2020 and was a success. It was able to scale and handle the incoming traffic.

To confirm that serverless worked well with other use cases, including the most complex ones, they did a second migration and **routed a part of live credit card transactions to the new serverless app**. If it worked with the live credit card traffic, it meant that the rest of the Treezor platform could run as serverless too.

That migration was also a success and cleared the way for migrating more APIs.

## Lift-and-shift the legacy monolith on AWS Lambda

The next step was to migrate the legacy PHP monolith from servers to AWS Lambda.

Doing [a complete rewrite was unrealistic](https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/). It would have meant completely halting all other developments, investing months (or even years) into the new system, and crossing fingers for the rewrite to actually be a success.

Instead, the team used Bref's [PHP-FPM runtime](/docs/runtimes/fpm-runtime). This AWS Lambda runtime runs PHP "as usual", like on any server, using PHP-FPM. It allowed taking the monolithic codebase and running it as an HTTP application on AWS Lambda.

To do a controlled migration, the team deployed the application both to the servers and to AWS Lambda. **Only 10 lines of code needed to be changed** to run the monolith on AWS Lambda.

Using API Gateway, they were able to **route some of the API traffic to AWS Lambda and the rest to the old stack running on servers**. In case of any issue, it was possible to roll back the endpoint to the server stack.

![](./treezor/treezor-1.svg)

In October 2021, they successfully migrated their first API route to AWS Lambda, the most critical one handling all live credit card transactions. Over the following year, more and more API endpoints were migrated away from the servers to AWS Lambda.

The migration of other API endpoints started in March 2022. Over the following year, the team migrated all API endpoints, cron tasks, and batch scripts to AWS Lambda. The servers were finally shut down in March 2023.

The entire migration took 1 year of planning and design, and 1 year of implementation. The main challenge was dealing with cron tasks running for more than 15 minutes (the maximum execution time on AWS Lambda). They needed to be split into smaller tasks. The Bref runtime also needed some customizations, for example to run custom PHP versions.

**The migration was a complete success**, and this is reflected in the key metrics tracked for the migration. With the new serverless stack:

- API response times were **2.5 times faster**.
- On-call alerts were **divided by 2** or even 3 times.
- API endpoint timeouts for card transactions were **reduced by a factor of 10**.

## Refactoring to serverless microservices

Now that the infrastructure was running smoothly, the team turned to the code itself. The goal: turning the legacy PHP monolith into a maintainable system.

Headcount in the Treezor IT department was growing and having all teams work on a single monolith was painful. The target architecture was set: a collection of **domain-oriented microservices**.

The use of API Gateway routing turned useful here too: it allowed applying the "strangler" pattern. As each domain was spun out into a separate service, the API Gateway routes could be transparently updated to point to the new services.

![](./treezor/treezor-2.svg)

The migration to microservices is still an ongoing work. As of today, the stack is **90% serverless on AWS** and deployed with Terraform.

Some new services are implemented in Go, but a majority are using PHP with Bref. API Gateway is used for [HTTP APIs](/docs/use-cases/http), and EventBridge is used for [asynchronous communication between services](/docs/use-cases/eventbridge). Other AWS services are used inside services to handle specific use cases, for example [job queues with SQS](/docs/use-cases/sqs), DynamoDB databases, SNS for parallelizing tasks, or even Kinesis for data pipelines.

## Conclusion

I find Treezor's story fascinating because it illustrates two very different use cases:

- Being able to **lift and shift** existing PHP applications to AWS Lambda with very few changes.
- And later going "all-in" on **event-driven microservices** and taking full advantage of what AWS has to offer.

It shows that both options are valid and have their own benefits.

First, **using AWS Lambda as a scalable PHP hosting platform works well**. For those who want scalability and simplicity, it is possible to avoid vendor lock-in and use AWS Lambda like any other hosting platform.

Second, AWS Lambda has great integrations with other AWS services. That allows building **event-driven microservices by composing the best AWS services for the use case**: SQS for infinitely scalable queues, EventBridge for asynchronous communication between services, DynamoDB for very optimized data storage, API Gateway for out-of-the-box caching, security and advanced routing for APIs... And much more of course.

Treezor's story also shows that the first option can be a good stepping stone to the second, removing some of the risk of a cloud migration.

Thank you [Treezor](https://www.treezor.com/), and thank you [Nicolas](https://www.linkedin.com/in/nicolasbordes/) and [Julien](https://www.linkedin.com/in/julien-mortuaire-29126528/) for sharing that story with us!

And if you want to work with Bref every day, [Treezor is hiring](https://www.welcometothejungle.com/fr/companies/treezor) 😉
1 change: 1 addition & 0 deletions docs/case-studies/treezor/treezor-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/case-studies/treezor/treezor-2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/deploy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Instead, let's remove development dependencies and optimize Composer's autoloade
composer install --prefer-dist --optimize-autoloader --no-dev
```

<Callout>
Using [aws/aws-sdk-php](https://github.com/aws/aws-sdk-php/tree/master/src/Script/Composer) or [google/apiclient](https://github.com/googleapis/google-api-php-client#cleaning-up-unused-services)? See the links for reducing deployment size by removing unused services
</Callout>

Now is also the best time to configure your project for production, as well as build any file cache if necessary.

Once your project is ready, you can deploy via the following command:
Expand Down
10 changes: 9 additions & 1 deletion docs/deploy/docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Bref helps you deploy using Docker images by offering base images that work on A
FROM bref/php-81-fpm:2

# Copy the source code in the image
COPY .. /var/task
COPY . /var/task

# Configure the handler file (the entrypoint that receives all HTTP requests)
CMD ["public/index.php"]
Expand All @@ -52,6 +52,14 @@ Bref offers the following base images:
- `bref/php-xx-console:2`: to run PHP CLI commands
- `bref/php-xx:2`: to run [PHP functions](../runtimes/function.mdx)

<Callout type="warning">
The `CMD` instruction in `Dockerfile` must contain a valid JSON array. This is why you must escape any `\` character. This is important for PHP class names, for example when using Laravel Octane:

```dockerfile filename="Dockerfile"
CMD ["Bref\\LaravelBridge\\Http\\OctaneHandler"]
```
</Callout>

### Extra PHP extensions

You can enable additional PHP extensions by pulling them from [Bref Extra Extensions](https://github.com/brefphp/extra-php-extensions):
Expand Down
3 changes: 1 addition & 2 deletions docs/environment/database-planetscale.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { NextSeo } from 'next-seo';
Amongst other features, it offers the following benefits compared to running a database on AWS:

- Simple to set up: no VPC (virtual private network) to set up, no instances to configure.
- Runs [the Vitess clustering system](https://planetscale.com/blog/vitess-for-the-rest-of-us), which offers better scalability and supports a lot more concurrent connections [via built-in connection pooling](https://planetscale.com/blog/one-million-connections).
- Offers a [free database in the Hobby plan](https://planetscale.com/pricing), and paid plans are usage-based.
- Runs [the Vitess clustering system](https://planetscale.com/blog/vitess-for-the-rest-of-us), which offers great scalability and supports a lot more concurrent connections [via built-in connection pooling](https://planetscale.com/blog/one-million-connections).
- Since it does not require a VPC, we do not need to set up and pay [for a NAT Gateway](database.mdx#accessing-the-internet).

One extra feature worth mentioning is [the branching concept](https://planetscale.com/docs/concepts/branching): it enables testing schema changes before deploying them in production without downtime.
Expand Down
26 changes: 26 additions & 0 deletions docs/environment/serverless-yml.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,32 @@ provider:
If you only want to define some permissions **per function**, instead of globally (ie: in the provider), you should install and enable the Serverless plugin [`serverless-iam-roles-per-function`](https://github.com/functionalone/serverless-iam-roles-per-function) and then use the `iamRoleStatements` at the function definition block.

## Stage parameters

Stage parameters are a great way to define values that change depending on the stage (dev, prod, staging…).

```yaml
params:
# Default parameters that apply to all stages
default:
# Here we use the special `sls:stage` variable
# to define a domain that changes depending on the stage
domain: ${sls:stage}.preview.myapp.com
# Parameters that apply to the prod stage
prod:
domain: myapp.com
# Parameters that apply to the dev stage
dev:
domain: preview.myapp.com

# Parameters can be used via the ${param:XXX} variables:
provider:
environment:
APP_DOMAIN: ${param:domain}
```
Read the full [Serverless documentation about stage parameters](https://github.com/serverless/serverless/blob/v3/docs/guides/dashboard/parameters.md#stage-parameters).
## Resources
```yaml
Expand Down
2 changes: 2 additions & 0 deletions docs/environment/variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ aws ssm put-parameter --region us-east-1 --name '//my-app\my-parameter' --type S

It is recommended to prefix the parameter name with your application name, for example: `/my-app/my-parameter`.

SSM also allows to store a SecureString parameter, which is encrypted with AWS KMS. To use a SecureString, simply change the `--type` argument to `--type SecureString`. Bref takes care of decrypting the value.

### Retrieving secrets

You can inject a secret in an environment variable:
Expand Down
12 changes: 11 additions & 1 deletion docs/laravel/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ At the moment, we deployed our local codebase to Lambda. When deploying for prod
- our local `.env` file,
- or any other dev artifact.

Follow [the deployment guide](/docs/deploy.md#deploying-for-production) for more details.
Follow [the deployment guide](/docs/deploy.md#deploying-for-production) for more details about deploying in general.

Specifically for Laravel, Bref will automatically cache the configuration on "cold starts". This means that you don't need to run `php artisan config:cache` before deploying. However, if you want to improve the cold start time you can pre-generate the config cache before deploying:

```bash
php artisan config:clear && php artisan config:cache
```

<Callout type="warning">
Laravel will hardcode absolute paths in the cached configuration. To deploy a valid cached configuration, you should run the commands above in Docker, with the application mounted in `/var/task` (the same path as on AWS Lambda).
</Callout>

## Troubleshooting

Expand Down
9 changes: 9 additions & 0 deletions docs/laravel/octane.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NextSeo } from 'next-seo';
import { Callout } from 'nextra/components';

<NextSeo description="Run Laravel with Octane on AWS Lambda using Bref." />

Expand All @@ -23,6 +24,14 @@ Keep the following details in mind:
- The process is kept alive between requests, but you still don't pay for time between requests. The execution model and cost model of AWS Lambda does not change (Lambda is frozen between requests).
- `BREF_LOOP_MAX` specifies the number of HTTP requests handled before the PHP process is restarted (and the memory is cleared).

<Callout>
If you deploy using [container images](../deploy/docker.mdx), you must escape the `\` characters in your `Dockerfile`:

```dockerfile filename="Dockerfile"
CMD ["Bref\\LaravelBridge\\Http\\OctaneHandler"]
```
</Callout>

## Persistent database connections

You can keep database connections persistent across requests to make your application even faster. To do so, set the `OCTANE_PERSIST_DATABASE_SESSIONS` environment variable:
Expand Down
3 changes: 3 additions & 0 deletions docs/laravel/passport.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ Instead, here is what you need to do:
serverless deploy
```

Note, that during deployment the keys will be stored at `./storage` path not at `/var/task/storage`. Workaround for this is to adjust Passport path with `Passport::loadKeysFrom('storage')`.


### Create tokens

Finally, you can create the tokens (which is the second part of the `passport:install` command):
Expand Down
Loading

0 comments on commit 8a0fb65

Please sign in to comment.