MVC Framework
EightFish embeds a thin MVC layer into it to make the programming life easier. If you come from the traditional Web developments, familiar with frameworks like Spring, Django, Flask, Express, Rocket and others, you will feel home when use EightFish.
In this page, we will use the Simple Example to demostrate.
App Entry
An EightFish app is actually a Spin redis-triggered application. The entry of this type of application looks like:
#[redis_component]
fn on_message(message: Bytes) -> Result<()> { }
You can look into the lib.rs file to learn the style.
The on_message
function is just a boilerplate to comply with. In its body, we need to build the instance of our app, and mount it to spin_worker::Worker
, and call worker.work(message)
to process this incoming message.
Every time the message comes (from the redis channel), the EightFish App handler will process this message, and give response to somewhere (some caches, which is defined by components spin_worker
and http_gate
together, not the channel message comes from) in redis.
App Instance
Every EightFish App should create an EightFish App instance, like:
pub fn build_app() -> EightFishApp {
let mut sapp = EightFishApp::new();
sapp.add_global_filter(Box::new(MyGlobalFilter))
.add_module(Box::new(article::ArticleModule));
sapp
}
The above function name is arbitrary, but should return the type of EightFishApp
. In its body we create the instance of EightFishApp
, mount global filter of the app and all modules implementing the application logic to this instance.
Global Filter
The global filter of EightFish is for all incoming requests. Some actions (like cookie verifying, authentication, etc.) should be checked before any specific logic being executed. So we need this mechanism to tackle them.
impl GlobalFilter for MyGlobalFilter {
fn before(&self, _req: &mut Request) -> EightFishResult<()> {
Ok(())
}
fn after(&self, _req: &Request, _res: &mut Response) -> EightFishResult<()> {
Ok(())
}
}
GlobalFilter
is a trait defined by EightFish SDK. And there are two methods in it: before()
and after()
. The before()
is used to process request ahead of any other biz logic code, and the after()
is used to process the response after all biz logic but before responding to user, it's the last step we can write something to attach or modify information to the response.
Modules
Every specific biz logic should be put into each specific module, and then mount these modules into the app instance described above. You can look into the article.rs file to get a concrete picture of the structure of a module.
First, you need to define a struct type, like:
pub struct ArticleModule;
and implement the Module
trait on it, to fill the router with some url endpoints:
impl Module for ArticleModule {
fn router(&self, router: &mut Router) -> Result<()> {
router.get("/article/:id", Self::get_one);
router.post("/article/new", Self::new_article);
router.post("/article/update", Self::update);
router.post("/article/delete/:id", Self::delete);
Ok(())
}
}
The above snippet fills the url router Router
by implementing the fn router
, the router supports two kinds of methods: get
and post
, correspond to the HTTP GET and POST methods respectively.
Besides that, in the trait Module
, we can implement the second level filters: before()
and after()
. These two filters apply on the url matches within this local module, not affecting other urls in other modules. So it is a kind of filter in module level. The EightFish::Module
is defined as follow:
pub trait EightFishModule: Sync + Send {
/// module before filter, will be executed before handler
fn before(&self, _req: &mut EightFishRequest) -> Result<()> {
Ok(())
}
/// module after filter, will be executed after handler
fn after(&self, _req: &EightFishRequest, _res: &mut EightFishResponse) -> Result<()> {
Ok(())
}
/// module router method, used to write router collection of this module here
fn router(&self, router: &mut EightFishRouter) -> Result<()>;
}
URL Handler
The corresponding handlers are implemented onto the module struct, as a method of this struct, like:
fn handler_name(req: &mut Request) -> Result<Response> {
Every url handler has unified function signature: a req: &mut Request
as parameter, and a Result<Response>
as function returning.
In the handler function, you can process the request at the third level.
Middleware Mechanism
In EightFish, middleware is just a normal function. It accepts the req: &mut Request
and return Result<()>
.
pub fn middleware_fn(req: &mut Request) -> Result<()> {
The middleware function could be placed in the global filter, module filter, or every handler function. This conduct a flexible three levels of middleware system.
Initial Globals
There is a method on EightFishApp: init_global()
. You can put the global variables which should exist as long as the EightFish app's lifecycle in it. This is a mechanism for shared data between different requests.
In the provided closure, you need to insert your desired data into the extension part of the request, as follows:
let a_global = Arc::new(Mutex::new(..))
...
app.init_global(|req: &mut Request| -> Result<()> {
req.ext_mut().insert("a_global", a_global);
})
...
Helper Macros
EightFish designs some helper macros to improve the experience of logic coding, especially on interacting with SQL db.
EightFish provides a derived macro named EightFishModel
for user's model.
For example, you just need to put this macro into the derive section above a model (struct) definition.
#[derive(Debug, Clone, Serialize, Deserialize, EightFishModel, Default)]
pub struct Article {
id: String,
title: String,
content: String,
authorname: String,
}
After that, the struct Article
will gain some powerful functionalities like:
fn model_name() -> String {}
fn field_names() -> String {}
fn build_insert_sql() -> String {}
fn from_row(row: Vec<DbValue>) -> #ident {}
...
These functionalities make you write biz code easily, rapidly and happily.
You can refer to the detailed inline doc here.
Run it
After all, switch into the app directory, and type:
spin up
to run this app up.