mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add a README tutorial on the mvc/overview example
Former-commit-id: c563c4f1ffa98705829e14b189a6976c3a6aa898
This commit is contained in:
parent
29d98ac281
commit
9922265454
17
_examples/mvc/overview/Dockerfile
Normal file
17
_examples/mvc/overview/Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
# docker build -t app .
|
||||
# docker run --rm -it -p 8080:8080 app:latest
|
||||
FROM golang:latest AS builder
|
||||
RUN apt-get update
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64
|
||||
WORKDIR /go/src/app
|
||||
COPY go.mod .
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go install
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /go/bin/app .
|
||||
ENTRYPOINT ["./app"]
|
377
_examples/mvc/overview/README.md
Normal file
377
_examples/mvc/overview/README.md
Normal file
|
@ -0,0 +1,377 @@
|
|||
# Quick start
|
||||
|
||||
The following guide is just a simple example of usage of some of the **Iris MVC** features. You are not limited to that data structure or code flow.
|
||||
|
||||
Create a folder, let's assume its path is `app`. The structure should look like that:
|
||||
|
||||
```
|
||||
│ main.go
|
||||
│ go.mod
|
||||
│ go.sum
|
||||
└───environment
|
||||
│ environment.go
|
||||
└───model
|
||||
│ request.go
|
||||
│ response.go
|
||||
└───database
|
||||
│ database.go
|
||||
│ mysql.go
|
||||
│ sqlite.go
|
||||
└───service
|
||||
│ greet_service.go
|
||||
└───controller
|
||||
greet_controller.go
|
||||
```
|
||||
|
||||
Navigate to that `app` folder and execute the following command:
|
||||
|
||||
```sh
|
||||
$ go init app
|
||||
$ go get -u github.com/kataras/iris/v12@master
|
||||
# or @latest for the latest official release.
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
Let's start by defining the available environments that our web-application can behave on.
|
||||
|
||||
We'll just work on two available environments, the "development" and the "production", as they define the two most common scenarios. The `ReadEnv` will read from the `Env` type of a system's environment variable (see `main.go` in the end of the page).
|
||||
|
||||
Create a `environment/environment.go` file and put the following contents:
|
||||
|
||||
```go
|
||||
package environment
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PROD Env = "production"
|
||||
DEV Env = "development"
|
||||
)
|
||||
|
||||
type Env string
|
||||
|
||||
func (e Env) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func ReadEnv(key string, def Env) Env {
|
||||
v := Getenv(key, def.String())
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
env := Env(strings.ToLower(v))
|
||||
switch env {
|
||||
case PROD, DEV: // allowed.
|
||||
default:
|
||||
panic("unexpected environment " + v)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func Getenv(key string, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
We will use two database management systems, the `MySQL` and the `SQLite`. The first one for "production" use and the other for "development".
|
||||
|
||||
Create a `database/database.go` file and copy-paste the following:
|
||||
|
||||
```go
|
||||
package database
|
||||
|
||||
import (
|
||||
"app/environment"
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
Exec(q string) error
|
||||
}
|
||||
|
||||
func NewDB(env environment.Env) DB {
|
||||
switch env {
|
||||
case environment.PROD:
|
||||
return &mysql{}
|
||||
case environment.DEV:
|
||||
return &sqlite{}
|
||||
default:
|
||||
panic("unknown environment")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's simulate our MySQL and SQLite `DB` instances. Create a `database/mysql.go` file which looks like the following one:
|
||||
|
||||
```go
|
||||
package database
|
||||
|
||||
import "fmt"
|
||||
|
||||
type mysql struct{}
|
||||
|
||||
func (db *mysql) Exec(q string) error {
|
||||
return fmt.Errorf("mysql: not implemented <%s>", q)
|
||||
}
|
||||
```
|
||||
|
||||
And a `database/sqlite.go` file.
|
||||
|
||||
```go
|
||||
package database
|
||||
|
||||
type sqlite struct{}
|
||||
|
||||
func (db *sqlite) Exec(q string) error { return nil }
|
||||
```
|
||||
|
||||
The `DB` depends on the `Environment.
|
||||
|
||||
> A practical and operational database example, including Docker images, can be found at the following guide: https://github.com/kataras/iris/tree/master/_examples/database/mysql
|
||||
|
||||
## Service
|
||||
|
||||
We'll need a service that will communicate with a database instance in behalf of our Controller(s).
|
||||
|
||||
In our case we will only need a single service, the Greet Service.
|
||||
|
||||
For the sake of the example, let's use two implementations of a greet service based on the `Environment`. The `GreetService` interface contains a single method of `Say(input string) (output string, err error)`. Create a `./service/greet_service.go` file and write the following code:
|
||||
|
||||
```go
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"app/database"
|
||||
"app/environment"
|
||||
)
|
||||
|
||||
// GreetService example service.
|
||||
type GreetService interface {
|
||||
Say(input string) (string, error)
|
||||
}
|
||||
|
||||
// NewGreetService returns a service backed with a "db" based on "env".
|
||||
func NewGreetService(env environment.Env, db database.DB) GreetService {
|
||||
service := &greeter{db: db, prefix: "Hello"}
|
||||
|
||||
switch env {
|
||||
case environment.PROD:
|
||||
return service
|
||||
case environment.DEV:
|
||||
return &greeterWithLogging{service}
|
||||
default:
|
||||
panic("unknown environment")
|
||||
}
|
||||
}
|
||||
|
||||
type greeter struct {
|
||||
prefix string
|
||||
db database.DB
|
||||
}
|
||||
|
||||
func (s *greeter) Say(input string) (string, error) {
|
||||
if err := s.db.Exec("simulate a query..."); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := s.prefix + " " + input
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type greeterWithLogging struct {
|
||||
*greeter
|
||||
}
|
||||
|
||||
func (s *greeterWithLogging) Say(input string) (string, error) {
|
||||
result, err := s.greeter.Say(input)
|
||||
fmt.Printf("result: %s\nerror: %v\n", result, err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The `greeter` will be used on "production" and the `greeterWithLogging` on "development". The `GreetService` depends on the `Environment` and the `DB`.
|
||||
|
||||
## Models
|
||||
|
||||
Continue by creating our HTTP request and response models.
|
||||
|
||||
Create a `model/request.go` file and copy-paste the following code:
|
||||
|
||||
```go
|
||||
package model
|
||||
|
||||
type Request struct {
|
||||
Name string `url:"name"`
|
||||
}
|
||||
```
|
||||
|
||||
Same for the `model/response.go` file.
|
||||
|
||||
```go
|
||||
package model
|
||||
|
||||
type Response struct {
|
||||
Message string `json:"msg"`
|
||||
}
|
||||
```
|
||||
|
||||
The server will accept a URL Query Parameter of `name` (e.g. `/greet?name=kataras`) and will reply back with a JSON message.
|
||||
|
||||
## Controller
|
||||
|
||||
MVC Controllers are responsible for controlling the flow of the application execution. When you make a request (means request a page) to MVC Application, a controller is responsible for returning the response to that request.
|
||||
|
||||
We will only need the `GreetController` for our mini web-application. Create a file at `controller/greet_controller.go` which looks like that:
|
||||
|
||||
```go
|
||||
package controller
|
||||
|
||||
import (
|
||||
"app/model"
|
||||
"app/service"
|
||||
)
|
||||
|
||||
type GreetController struct {
|
||||
Service service.GreetService
|
||||
// Ctx iris.Context
|
||||
}
|
||||
|
||||
func (c *GreetController) Get(req model.Request) (model.Response, error) {
|
||||
message, err := c.Service.Say(req.Name)
|
||||
if err != nil {
|
||||
return model.Response{}, err
|
||||
}
|
||||
|
||||
return model.Response{Message: message}, nil
|
||||
}
|
||||
```
|
||||
|
||||
The `GreetController` depends on the `GreetService`. It serves the `GET: /greet` index path through its `Get` method. The `Get` method expecting a `model.Request` which contains a single field name of `Name` which will be extracted from the `URL Query Parameter 'name'` (because it's a `GET` requst and its `url:"name"` struct field).
|
||||
|
||||
## Wrap up
|
||||
|
||||
```sh
|
||||
+-------------------+
|
||||
| Env (DEV, PROD) |
|
||||
+---------+---------+
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
DEV | | | PROD
|
||||
-------------------+---------------------+ | +----------------------+-------------------
|
||||
| | |
|
||||
| | |
|
||||
+---+-----+ +----------------v------------------+ +----+----+
|
||||
| sqlite | | NewDB(Env) DB | | mysql |
|
||||
+---+-----+ +----------------+---+--------------+ +----+----+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+--------------+-----+ +-------------------v---v-----------------+ +----+------+
|
||||
| greeterWithLogging | | NewGreetService(Env, DB) GreetService | | greeter |
|
||||
+--------------+-----+ +---------------------------+-------------+ +----+------+
|
||||
| | |
|
||||
| | |
|
||||
| +-----------------------------------------+ |
|
||||
| | GreetController | | |
|
||||
| | | | |
|
||||
| | - Service GreetService <-- | |
|
||||
| | | |
|
||||
| +-------------------+---------------------+ |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| +-----------+-----------+ |
|
||||
| | HTTP Request | |
|
||||
| +-----------------------+ |
|
||||
| | /greet?name=kataras | |
|
||||
| +-----------+-----------+ |
|
||||
| | |
|
||||
+------------------+--------+ +------------+------------+ +-------+------------------+
|
||||
| model.Response (JSON) | | Response (JSON, error) | | Bad Request |
|
||||
+---------------------------+ +-------------------------+ +--------------------------+
|
||||
| { | | mysql: not implemented |
|
||||
| "msg": "Hello kataras" | +--------------------------+
|
||||
| } |
|
||||
+---------------------------+
|
||||
```
|
||||
|
||||
Now it's the time to wrap all the above into our `main.go` file. Copy-paste the following code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"app/controller"
|
||||
"app/database"
|
||||
"app/environment"
|
||||
"app/service"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/mvc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/ping", pong).Describe("healthcheck")
|
||||
|
||||
mvc.Configure(app.Party("/greet"), setup)
|
||||
|
||||
// http://localhost:8080/greet?name=kataras
|
||||
app.Listen(":8080", iris.WithLogLevel("debug"))
|
||||
}
|
||||
|
||||
func pong(ctx iris.Context) {
|
||||
ctx.WriteString("pong")
|
||||
}
|
||||
|
||||
func setup(app *mvc.Application) {
|
||||
// Register Dependencies.
|
||||
app.Register(
|
||||
environment.DEV, // DEV, PROD
|
||||
database.NewDB, // sqlite, mysql
|
||||
service.NewGreetService, // greeterWithLogging, greeter
|
||||
)
|
||||
|
||||
// Register Controllers.
|
||||
app.Handle(new(controller.GreetController))
|
||||
}
|
||||
```
|
||||
|
||||
The `mvc.Application.Register` method registers one more dependencies, dependencies can depend on previously registered dependencies too. Thats the reason we pass, first, the `Environment(DEV)`, then the `NewDB` which depends on that `Environment`, following by the `NewGreetService` function which depends on both the `Environment(DEV)` and the `DB`.
|
||||
|
||||
The `mvc.Application.Handle` registers a new controller, which depends on the `GreetService`, for the targeted sub-router of `Party("/greet")`.
|
||||
|
||||
## Run
|
||||
|
||||
Install [Go](https://golang.org/dl) and run the application with:
|
||||
|
||||
```sh
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Install [Docker](https://www.docker.com/) and execute the following command:
|
||||
|
||||
```sh
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
Visit http://localhost:8080?name=kataras.
|
||||
|
||||
Now, replace the `main.go`'s `app.Register(environment.DEV` with `environment.PROD`, restart the application and refresh. You will see that a new database (`sqlite`) and another service of (`greeterWithLogging`) will be binded to the `GreetController`. With **a single change** you achieve to completety change the result.
|
16
_examples/mvc/overview/docker-compose.yml
Normal file
16
_examples/mvc/overview/docker-compose.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
version: '3.1'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
PORT: 8080
|
||||
ENVIRONMENT: development
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
|
@ -1,10 +1,50 @@
|
|||
package environment
|
||||
|
||||
// Env is the environment type.
|
||||
type Env string
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Available environments example.
|
||||
const (
|
||||
PROD Env = "production"
|
||||
DEV Env = "development"
|
||||
)
|
||||
|
||||
// Env is the environment type.
|
||||
type Env string
|
||||
|
||||
// String just returns the string representation of the Env.
|
||||
func (e Env) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// ReadEnv returns the environment of the system environment variable of "key".
|
||||
// Returns the "def" if not found.
|
||||
// Reports a panic message if the environment variable found
|
||||
// but the Env is unknown.
|
||||
func ReadEnv(key string, def Env) Env {
|
||||
v := Getenv(key, def.String())
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
|
||||
env := Env(strings.ToLower(v))
|
||||
switch env {
|
||||
case PROD, DEV: // allowed.
|
||||
default:
|
||||
panic("unexpected environment " + v)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// Getenv returns the value of a system environment variable "key".
|
||||
// Defaults to "def" if not found.
|
||||
func Getenv(key string, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
module app
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/kataras/iris/v12 v12.1.9-0.20200621141528-f6355c9716ab
|
||||
|
|
|
@ -12,10 +12,17 @@ import (
|
|||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
app.Get("/ping", pong).Describe("healthcheck")
|
||||
|
||||
mvc.Configure(app.Party("/greet"), setup)
|
||||
|
||||
// http://localhost:8080/greet?name=kataras
|
||||
app.Listen(":8080", iris.WithLogLevel("debug"))
|
||||
addr := ":" + environment.Getenv("PORT", ":8080")
|
||||
app.Listen(addr, iris.WithLogLevel("debug"))
|
||||
}
|
||||
|
||||
func pong(ctx iris.Context) {
|
||||
ctx.WriteString("pong")
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -35,9 +42,9 @@ func main() {
|
|||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+------------+-----+ +-------------------v---v-----------------+ +----+------+
|
||||
| greeterWithLog | | NewGreetService(Env, DB) GreetService | | greeter |
|
||||
-------------+-----+ +---------------------------+-------------+ +----+------+
|
||||
+--------------+-----+ +-------------------v---v-----------------+ +----+------+
|
||||
| greeterWithLogging | | NewGreetService(Env, DB) GreetService | | greeter |
|
||||
+--------------+-----+ +---------------------------+-------------+ +----+------+
|
||||
| | |
|
||||
| | |
|
||||
| +-----------------------------------------+ |
|
||||
|
@ -66,8 +73,9 @@ func main() {
|
|||
func setup(app *mvc.Application) {
|
||||
// Register Dependencies.
|
||||
// Tip: A dependency can depend on other dependencies too.
|
||||
env := environment.ReadEnv("ENVIRONMENT", environment.DEV)
|
||||
app.Register(
|
||||
environment.DEV, // DEV, PROD
|
||||
env, // DEV, PROD
|
||||
database.NewDB, // sqlite, mysql
|
||||
service.NewGreetService, // greeterWithLogging, greeter
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"app/database"
|
||||
"app/environment"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GreetService example service.
|
||||
|
|
Loading…
Reference in New Issue
Block a user