-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Serverless APIs and functions #2
Comments
Can I hit my Celest functions via REST requests? For things like your Dart native server errors (very cool btw), what do those look like when I hit a Celest API via REST and it errors? Context: we have a mess of Python scripts that we're trying to run on a schedule. I'd be interesting in migrating them to Celest and then triggering them via HTTP request (though I imagine longterm Celest will support triggering functions on a schedule out of the box?) |
Update: I've added a new section to the RFC titled Calling Celest APIs with HTTP requests which summarizes these points. Hey Casey! Great questions. Here's what we're thinking on that front.
Yes! Celest functions are exposed as HTTP endpoints which can be called via any HTTP client from any programming language or toolchain. The HTTP conventions for Celest functions are:
When a cloud function fails with an exception or error, the response will carry a 4xx/5xx status code and JSON body with an For example, if a function throws the
However, if the function threw a
We have considered this and we have decided for our initial release not to allow configuration of HTTP abstractions in Celest APIs including response codes. We think that fully leveraging Dart's idioms provides the best developer experience and that exposing HTTP/REST semantics may take away from that goal. But we would love to hear if you think there is still an opportunity to improve the offering with status codes or other configurations given the examples above.
You're absolutely correct that we intend to introduce scheduled functions and other event patterns natively in Celest. For the time being, it sounds like you'll be able to use deployed Celest functions in your python scripts, though please let me know if it seems like there would be any sticking points with that integration. Thanks for the awesome feedback! |
Np, and your conclusion-to hold off on any more complex status code features-seems pretty reasonable. The "as if it were a local function" level of experience you're working towards seems like an ideal selling point-and I could still do my goofy REST stuff off to the side while I wait for more native cron scheduling features. A separate thought: To give concrete examples:
Oh crap. v2 has a bug in one of its server functions. I want to rush out a hotfix without having to wait for a new client to go out. How do I now deploy a new version of my functions and ensure my previous client hits the new functions? And then, how do I avoid server versioning and client versioning diverging to the point where it's really hard to remember the associations between the two? |
Hi @caseycrogers - we are thinking of a pretty similar pattern for scheduling cloud functions. We want to make sure we are doing it in a way that would be consistent across different parts of the features we introduce. That is a topic in one of our future RFC around event-driven patterns.
For adding changes that would be considered breaking to your existing apps, we are still thinking about the best pattern to adopt to provide a path forward for developers iterating on their deployed backend. We will share our thoughts in future RFCs. |
Hi @dnys1 , @abdallahshaban557 ! I have a couple of questions:
Keep up the good work, I'm really hyped for this 🚀 |
Hey @filippomenchini 👋 thanks for your comment!
The CLI creates serializers for your classes in a very similar way to how packages like
Yes! We are exploring the best way to enable this pattern for developers and will be happy to share more specifics in an upcoming RFC. To better understand your setup, are you envisioning that your Flutter/Dart clients would be in wholly separate Git repos from the Celest backend? If so, would it be a dealbreaker if Celest only supported monorepos where your client + backend code lived in a single repository?
Yes, we are huge fans of gRPC and actually make use of it in our backend! We chose not to implement Celest APIs on top of gRPC for a few reasons:
I can say that we hope to bring all the best features of gRPC to Celest but expressed via Dart instead of Protobuf! |
Thanks for the quick response!
Got it, sounds great!
Yes, I was thinking about many Flutter/Dart clients linked to a single backend that is managed in a independent repo, this could be useful in some situations, but it wouldn't be a dealbreaker if Celest only supported monorepos, you could always have a folder with the Celest code and many others containing the applications. With Celest this should be pretty easy to maintain, unlike AWS Amplify or Firebase that required you to be able to switch programming languages every 2 seconds.
This makes sense, maybe you could tackle this in the future, REST APIs are more accessible and much easier to implement! |
Thanks for sharing more about your envisioned repo setup! For Celest, we are really excited about the experience we can provide in monorepos, including a simple and intuitive CI/CD experience. That being said, we're not closing the door to other ways people build their apps. We'll definitely keep your feedback in mind as we build and get your insights when we've fleshed out more.
We'll keep this in mind too. Part of our vision with Celest is to simply find the right abstractions for RPC-style communication. I think if we can nail that, then the door is wide open for extending support to other communication patterns like gRPC 🚀 |
When Celest will be released, I'll be happy to try the multi-repos setup to give you a feedback 🚀 |
|
We feel the same. Top of mind for us for sure. The experience that Casey mentioned earlier in this RFC is pretty similar to what we have so far.
We are thinking about queuing and creating abstractions for enabling event-driven applications. Would love to understand more which use cases you are hoping it unlocks for you.
We haven't thought deeply about a distributed cache per se, but we have been chatting about offline first for Flutter apps. Were you hoping for caching to solve for on-device caching? or something else?
Websockets are going to be in a future RFC that we will share. We have some initial ideas but wanted to spend a bit more time fleshing it out and walking through more scenarios. We want to get the right experience down including Pub/Sub patterns and applying authorization. |
It might be useful to have a |
Hello @chimon2000 So you would prefer to use the |
|
Hi @dnys1 @abdallahshaban557 - Great going so far. Can you provide some examples for implementing authentication based api? And then only authorised users are able to trigger example greetings api? |
@dishankjindal1 - yes! We will provide that in a future RFC. Since we won't have an Authentication solution so far, what do you use as your primary Authentication provider that you would want to bring in and use with Celest? @marcglasberg - Dillon and I are discussing some of your questions. Will get back to you ASAP. |
Hey @abdallahshaban557 - I am assuming, all 0Auth 2.0, OIDC based service providers work the same. So if you can create an generic abstraction class in celest and developers can use that abstract to plug in there service providers callbacks, api tokens, etc. Hence covering all services providers will be a great addition. In my workplace, I have used both Auth0 and AWS cognito based implementations. But because I am on the frontend I am not sure how they are managing the resources. I just need few APIs,
For state management,
Cc: @dnys1 |
@marcglasberg Thanks for your awesome feedback!
Great question. For retry, we plan to handle that automatically for developers. Transient errors will be exposed in such a way that they can be caught and handled by developers. For idempotency, what would you think about an
Functions are reused. That might change in the future and we will be treating it as an implementation detail. We recommend developers to not use global state for caching in their functions.
We’re basically using
Abdallah and I worked on AWS Amplify Flutter where we had an offline first solution - and we agree with that sentiment. Developers have to opt in to it. Ideally, we want to allow caching/offline first even at the model level to provide the best level of control for developers. Agreed as well that this will be one of the more difficult problem to solve for, but we’re excited for the challenge.
We are exploring that! A dev tools extension also seem like a really cool alternative. Still brainstorming at this point so we appreciate the feedback.
That sounds great! Looking forward to discussing it more. Also feel free to reach out if you would like to schedule a call to go over it in more depth.
For Auth, our thought process is that we will offer developers magic links, WebAuthN, and social sign-in. You will also be able to integrate any custom OIDC-compliant identity provider. During our time at AWS Amplify, we built the Authenticator UI component which took care of all the UI flows, so we understand first-hand the value of offering that for developers. |
Hi @dishankjindal1 - We are thinking very similarly in terms of integrating with OAuth2 and OIDC-compliant providers. This piece will be in one of the future RFCs we share since that is how we expect customers will control access to their APIs in the absence of Celest having an Authentication solution in the short term. |
Yes, in our apps normally we use a |
It'd be nice if the offline first solution were pluggable. Powersync for example integrates with any Postgres database and the client essentially just uses their SQLite library. |
Hi @chimon2000 , the environment variables we want to help you manage are all related to information you want stored and used only in your backend. We have been revising our experience, and here is what we landed on so far. Setting environment variables:
After setting the environment variables, the CLI will take care of generating the types needed for you to inject the environment variables into your cloud functions. Getting environment variables:
Please let us know what you think - we've taken inspiration from the Vercel link you sent out. On your point about keeping the offline logic pluggable, we've taken note of that from what last time we've chatted. We will for sure keep it in mind! |
@dnys1 @abdallahshaban557 Do you mean idempotency would work like described here? https://stripe.com/docs/api/idempotent_requests |
Hi @marcglasberg - yes, that is exactly what we mean! |
A question:
I see you don't need to annotate the functions to turn them into cloud functions. But what if I want a cloud function to call a local function?
Am I correct to assume this wouldn't work, as it would try to turn Or will it actually work, by only turning into cloud functions those containing |
Hi Marcelo, good observation! To define local functions which are not cloud functions, there are two options available:
Any top-level, non-private functions within the |
This RFC is intended to collect feedback on Celest’s developer experience around serverless APIs and functions. We are sharing it as early as we can to give you the ability to provide feedback about what we’re building. Please take this opportunity to share your thoughts. Your input will help tremendously in making sure we deliver an experience you'll be delighted to use.
We will cover 3 main topics:
Getting started with Celest
Prerequisites
To use Celest in your Flutter app, you need the following prerequisites:
flutter create
commandNote
This is an example and will not work just yet. Join the waitlist to get access when we launch!
That’s it! You do not need any additional tooling to build, test, and deploy your backend.
Setting up the Celest CLI
After installing the Celest CLI, navigate to the root of your Flutter project and run the following command.
You will be prompted to sign in using GitHub. Once the authentication with GitHub is successful, a watch command will continue to run in your CLI to detect changes made to your Celest backend definition and code-generate a Dart client for you in the following path
<flutter_app>/lib/celest/client.dart
to test your changes locally. We will cover later how to use the code-generated client after defining your APIs and cloud functions.The CLI will also create a folder in your project called
celest
, which will include the following files.Creating serverless APIs and cloud functions
Creating serverless APIs and functions with Celest enables you to connect and aggregate information from different parts of your backend, and build custom business logic that runs completely in the cloud. You define your cloud functions as Dart functions, and Celest takes care of setting up and managing the backend infrastructure around them.
To get started with building your first API, navigate to the
<flutter_app>/celest/apis/
folder and create a file named<api_name>.dart
. You can create as many API files as you want in this directory.Tip
Access to your APIs is denied by default. What this means is that you’ll need to add the
@api.anonymous()
annotationto the top of the file for APIs to be publicly accessible.
The above code snippet is all you need to define your cloud functions! When the
celest start
command runs, a local environment is spun up and a Dart client is generated to help you connect to the local backend.Below is an example of how you would use the generated client in your
main.dart
file.Using middleware for your APIs and cloud functions
Middleware enables you to have logic that can run before and/or after your cloud function executes. In Celest, you can define your own middleware and attach it to all functions in an API or to specific cloud functions.
To define your middleware, go to your
<flutter_app>/celest/apis/
folder, and create amiddlware.dart
file (the name of the file is up to you). The following code snippet shows two middleware for logging requests and responses being defined.To attach a middleware to a cloud function, annotate the function with an instance of the middleware. The following is an example of using the request and response logging middleware in an API.
You can alternatively set up middleware to run for all functions inside an API file by applying the middleware annotation at the top of your API file as shown below.
You also have the option to compose middleware by applying multiple middleware to the API or cloud function. In the following example, four middleware are composed and will execute in top-down order. When a user calls
sayHello
, the execution order of the middleware will be:first
,second
,third
, thenfourth
.Since middleware can apply logic before and after a function runs, the composition of the middleware can be thought of as a sandwich. That means, in the previous example,
middleware.first
runs both first and last if it defines both pre- and post-handler logic.@middleware.first
pre-handler logic runs@middleware.second
pre-handler logic runs@middleware.third
pre-handler logic runs@middleware.fourth
pre-handler logic runssayHello
runs@middleware.fourth
post-handler logic runs@middleware.third
post-handler logic runs@middleware.second
post-handler logic runs@middleware.first
post-handler logic runsLogging in cloud functions
Celest enables you to use logging to capture important information as your functions execute. Within your function code, you can use
print()
statements or a custom logger which prints to the terminal. These logs will appear in the same terminal where thecelest start
command is running and be accessible when running remotely post-deploy.Here is an example of using print statements in your cloud function definition.
When you call the cloud function, these logs will be streamed locally.
Creating custom exceptions
You can create custom exception types in your backend to control how APIs and cloud functions behave when there are errors. This enables you to have clear exceptions thrown in your Flutter app that you can react to.
Below is an example of how to define a custom exception. You can create exceptions in any folder inside your
celest
folder. For this example, the exception type is defined in<flutter_app>/celest/apis/my_exception.dart
.You can then throw these exceptions in your functions whenever needed as shown below.
In your Flutter app, the same
MyException
type will be thrown by the generated client if an error occurs.Supported data types
With Celest serverless APIs and functions, serialization is handled out-of-the-box in most cases. In situations requiring custom serialization, we support any custom classes that you’re already using without any extra setup.
Imagine you're working on an e-commerce application with an
Order
class defined in your codebase.You can use this
Order
type in any cloud function as both a parameter or return value, without the need to manually add serialization logic.When communicating with your backend, Celest will serialize the
Order
class as a JSON map with the field names as keys.If you need custom handling over serialization logic, add a
fromJson
constructor andtoJson
method to your datatype. Celest will use your customfromJson
/toJson
implementations instead when transmitting the type to and from your backend.Note
Here, the
Price.toJson
method is used to upper-case thecurrency
value.Interoperability with Dart packages
Your cloud functions are pure Dart functions. They are compiled to run natively on Linux and so any Dart packages which can be used on Linux can be used in your functions.
For example, to communicate with systems outside of Celest, you can use the
http
package,dio
, or any other HTTP package you are familiar with.To add a package for use in your backend, run the
dart pub add
command from thecelest
folder.Environment variables
Environment variables can be used to provide environment-specific configuration to your backend. They allow you to keep their values separate from your codebase, improving flexibility when running in different environments.
To set up environment variables in your backend, navigate to the
<flutter_app>/celest/config/env.dart
file and list all the variables you’ll need throughout your backend.To ensure a cloud function has access to the variable when it runs, pass it as a parameter and annotate with the variable definition. Here, the greeting service URL will be securely injected by the server when your function starts.
Note
Annotated parameters (like
greetingUrl
) will not appear in the generated client, but can be used in your backend when unit testing and mocking (see Testing your backend resources below).Setting up environment variable values locally
When you run
celest start
orcelest deploy
, the CLI will look for values of the environment variables in your shell environment. For any variables not defined, the CLI will prompt you for their values.To change the values of environment variables previously defined, re-export the value from your terminal before running
celest start
orcelest deploy
.Celest will detect the presence of a new value and update your local/deployed function to reflect the change.
Testing your backend resources
The serverless functions and APIs you define are Dart functions and can be tested like any other. Within your
celest
folder, write unit tests for your functions usingpackage:test
or any other Dart testing framework.Deploying your backend resources
When you have tested and validated your backend locally, use the Celest CLI to deploy your backend resources to the cloud.
Calling Celest APIs with HTTP requests
If you'd like to use your Celest APIs outside of your Flutter/Dart app, you still can! Celest functions are exposed as HTTP endpoints which can be called via any HTTP client from any programming language or toolchain.
The HTTP conventions for Celest functions are:
When a cloud function fails with an exception or error, the response will carry a 4xx/5xx status code and JSON body with an
error
key. If the exception is a user-defined exception type, theerror
field itself is encoded as a JSON message.For example, if a function throws the
MyException
type defined in the example above, the response would be:However, if the function threw a
StateError
, it would look like this where the error is stringified in theerror
field.Next steps
Thank you for your time and for making it this far! We greatly appreciate any feedback you provide on our developer experience.
If there’s anything we’ve left out or things you would like to see discussed, please us know! Shortly, we will share additional RFCs to cover:
Thank you for coming on this journey with us 💙 We are so excited to bring you these features and more very soon! 🚀
If you haven’t already, please make sure to sign up for our waitlist to get the latest updates on our progress and follow us on Twitter/X where we share more insights and behind-the-scenes snippets.
The text was updated successfully, but these errors were encountered: