add a new 'overview' MVC example

Former-commit-id: f73cbf6010595c639f6c5b5119e2ec41bc9802a5
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-06-21 00:12:07 +03:00
parent 45c6bce15f
commit d55bb34766
29 changed files with 361 additions and 148 deletions

View File

@ -2,7 +2,7 @@
> This is the under-development branch. Stay tuned for the upcoming release [v12.2.0](HISTORY.md#Next).
![](https://iris-go.com/images/release.png) Iris version **12.1.8** has been [released](HISTORY.md#su-16-february-2020--v1218)!
<!-- ![](https://iris-go.com/images/release.png) Iris version **12.1.8** has been [released](HISTORY.md#su-16-february-2020--v1218)! -->
![](https://iris-go.com/images/cli.png) The official [Iris Command Line Interface](https://github.com/kataras/iris-cli) will soon be near you in 2020!
@ -31,7 +31,7 @@ Learn what [others saying about Iris](https://iris-go.com/testimonials/) and **[
```sh
# https://github.com/kataras/iris/wiki/Installation
$ go get github.com/kataras/iris/v12@latest
$ go get github.com/kataras/iris/v12@master
# assume the following code in example.go file
$ cat example.go
```
@ -67,7 +67,7 @@ Iris contains extensive and thorough **[wiki](https://github.com/kataras/iris/wi
<!-- ![](https://media.giphy.com/media/Ur8iqy9FQfmPuyQpgy/giphy.gif) -->
For a more detailed technical documentation you can head over to our [godocs](https://pkg.go.dev/github.com/kataras/iris/v12@v12.2.0). And for executable code you can always visit the [\_examples](_examples/) repository's subdirectory.
For a more detailed technical documentation you can head over to our [godocs](https://godoc.org/github.com/kataras/iris). And for executable code you can always visit the [./_examples](_examples) repository's subdirectory.
### Do you like to read while traveling?

View File

@ -182,7 +182,8 @@
* [JWT](dependency-injection/jwt/main.go)
* [JWT (iris-contrib)](dependency-injection/jwt/contrib/main.go)
* MVC
* [Overview - Repository and Service layers](mvc/overview)
* [Overview](mvc/overview)
* [Repository and Service layers](mvc/repository)
* [Hello world](mvc/hello-world/main.go)
* [Basic](mvc/basic/main.go)
* [Wildcard](mvc/basic/wildcard/main.go)

View File

@ -0,0 +1,23 @@
package controller
import (
"app/model"
"app/service"
)
// GreetController handles the index.
type GreetController struct {
Service service.GreetService
// Ctx iris.Context
}
// Get serves [GET] /.
// Query: name
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
}

View File

@ -0,0 +1,22 @@
package database
import (
"app/environment"
)
// DB example database interface.
type DB interface {
Exec(q string) error
}
// NewDB returns a database based on "env".
func NewDB(env environment.Env) DB {
switch env {
case environment.PROD:
return &mysql{}
case environment.DEV:
return &sqlite{}
default:
panic("unknown environment")
}
}

View File

@ -0,0 +1,10 @@
package database
import "fmt"
type mysql struct{}
func (db *mysql) Exec(q string) error {
// simulate an error response.
return fmt.Errorf("mysql: not implemented <%s>", q)
}

View File

@ -0,0 +1,5 @@
package database
type sqlite struct{}
func (db *sqlite) Exec(q string) error { return nil }

View File

@ -0,0 +1,10 @@
package environment
// Env is the environment type.
type Env string
// Available environments example.
const (
PROD Env = "production"
DEV Env = "development"
)

View File

@ -0,0 +1,3 @@
module app
go 1.14

View File

@ -1,13 +1,10 @@
// file: main.go
package main
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datasource"
"github.com/kataras/iris/v12/_examples/mvc/overview/repositories"
"github.com/kataras/iris/v12/_examples/mvc/overview/services"
"github.com/kataras/iris/v12/_examples/mvc/overview/web/controllers"
"github.com/kataras/iris/v12/_examples/mvc/overview/web/middleware"
"app/controller"
"app/database"
"app/environment"
"app/service"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
@ -15,39 +12,66 @@ import (
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
mvc.Configure(app.Party("/greet"), setup)
// Load the template files.
app.RegisterView(iris.HTML("./web/views", ".html"))
// Serve our controllers.
mvc.New(app.Party("/hello")).Handle(new(controllers.HelloController))
// You can also split the code you write to configure an mvc.Application
// using the `mvc.Configure` method, as shown below.
mvc.Configure(app.Party("/movies"), movies)
// http://localhost:8080/hello
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Listen(":8080", iris.WithOptimizations)
// http://localhost:8080/greet?name=kataras
app.Listen(":8080", iris.WithLogLevel("debug"))
}
// note the mvc.Application, it's not iris.Application.
func movies(app *mvc.Application) {
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
app.Router.Use(middleware.BasicAuth)
/*
+-------------------+
| Env (DEV, PROD) |
+---------+---------+
| | |
| | |
| | |
DEV | | | PROD
-------------------+---------------------+ | +----------------------+-------------------
| | |
| | |
+---+-----+ +----------------v------------------+ +----+----+
| sqlite | | NewDB(Env) DB | | mysql |
+---+-----+ +----------------+---+--------------+ +----+----+
| | | |
| | | |
| | | |
+------------+-----+ +-------------------v---v-----------------+ +----+------+
| greeterWithLog | | 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" | +--------------------------+
| } |
+---------------------------+
*/
func setup(app *mvc.Application) {
// Register Dependencies.
// Tip: A dependency can depend on other dependencies too.
app.Register(
environment.DEV, // DEV, PROD
database.NewDB, // sqlite, mysql
service.NewGreetService, // greeterWithLogging, greeter
)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie app's dependencies.
movieService := services.NewMovieService(repo)
app.Register(movieService)
// serve our movies controller.
// Note that you can serve more than one controller
// you can also create child mvc apps using the `movies.Party(relativePath)` or `movies.Clone(app.Party(...))`
// if you want.
app.Handle(new(controllers.MovieController))
// Register Controllers.
app.Handle(new(controller.GreetController))
}

View File

@ -0,0 +1,6 @@
package model
// Request example incoming request.
type Request struct {
Name string `json:"name" url:"name"`
}

View File

@ -0,0 +1,6 @@
package model
// Response example server's response.
type Response struct {
Message string `json:"msg"`
}

View File

@ -0,0 +1,50 @@
package service
import (
"app/database"
"app/environment"
"fmt"
)
// 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
}

View File

@ -2,7 +2,7 @@
package datasource
import "github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
import "github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
// Movies is our imaginary data source.
var Movies = map[int64]datamodels.Movie{

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,53 @@
// file: main.go
package main
import (
"github.com/kataras/iris/v12/_examples/mvc/repository/datasource"
"github.com/kataras/iris/v12/_examples/mvc/repository/repositories"
"github.com/kataras/iris/v12/_examples/mvc/repository/services"
"github.com/kataras/iris/v12/_examples/mvc/repository/web/controllers"
"github.com/kataras/iris/v12/_examples/mvc/repository/web/middleware"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
)
func main() {
app := iris.New()
app.Logger().SetLevel("debug")
// Load the template files.
app.RegisterView(iris.HTML("./web/views", ".html"))
// Serve our controllers.
mvc.New(app.Party("/hello")).Handle(new(controllers.HelloController))
// You can also split the code you write to configure an mvc.Application
// using the `mvc.Configure` method, as shown below.
mvc.Configure(app.Party("/movies"), movies)
// http://localhost:8080/hello
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Listen(":8080", iris.WithOptimizations)
}
// note the mvc.Application, it's not iris.Application.
func movies(app *mvc.Application) {
// Add the basic authentication(admin:password) middleware
// for the /movies based requests.
app.Router.Use(middleware.BasicAuth)
// Create our movie repository with some (memory) data from the datasource.
repo := repositories.NewMovieRepository(datasource.Movies)
// Create our movie service, we will bind it to the movie app's dependencies.
movieService := services.NewMovieService(repo)
app.Register(movieService)
// serve our movies controller.
// Note that you can serve more than one controller
// you can also create child mvc apps using the `movies.Party(relativePath)` or `movies.Clone(app.Party(...))`
// if you want.
app.Handle(new(controllers.MovieController))
}

View File

@ -1,20 +1,20 @@
# Domain Models
There should be the domain/business-level models.
Example:
```go
import "github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
type Movie struct {
datamodels.Movie
}
func (m Movie) Validate() (Movie, error) {
/* do some checks and return an error if that Movie is not valid */
}
```
However, we will use the "datamodels" as the only one models package because
# Domain Models
There should be the domain/business-level models.
Example:
```go
import "github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
type Movie struct {
datamodels.Movie
}
func (m Movie) Validate() (Movie, error) {
/* do some checks and return an error if that Movie is not valid */
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure we don't need any extra functionality or validation inside it.

View File

@ -1,3 +1,3 @@
# Repositories
# Repositories
The package which has direct access to the "datasource" and can manipulate data directly.

View File

@ -6,7 +6,7 @@ import (
"errors"
"sync"
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
)
// Query represents the visitor and action queries.

View File

@ -1,3 +1,3 @@
# Service Layer
# Service Layer
The package which has access to call functions from the "repositories" and "models" ("datamodels" only in that simple example). It should contain the domain logic.

View File

@ -3,8 +3,8 @@
package services
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/overview/repositories"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/repositories"
)
// MovieService handles some of the CRUID operations of the movie datamodel.

View File

@ -5,8 +5,8 @@ package controllers
import (
"errors"
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/overview/services"
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12/_examples/mvc/repository/services"
"github.com/kataras/iris/v12"
)

View File

@ -1,56 +1,56 @@
# View Models
There should be the view models, the structure that the client will be able to see.
Example:
```go
import (
"github.com/kataras/iris/v12/_examples/mvc/overview/datamodels"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
/* do some checks and return true if it's valid... */
return m.ID > 0
}
```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoretically, something like the following is permitted if it's really necessary;
```go
// Dispatch completes the `kataras/iris/mvc#Result` interface.
// Sends a `Movie` as a controlled http response.
// If its ID is zero or less then it returns a 404 not found error
// else it returns its json representation,
// (just like the controller's functions do for custom types by default).
//
// Don't overdo it, the application's logic should not be here.
// It's just one more step of validation before the response,
// simple checks can be added here.
//
// It's just a showcase,
// imagine the potentials this feature gives when designing a bigger application.
//
// This is called where the return value from a controller's method functions
// is type of `Movie`.
// For example the `controllers/movie_controller.go#GetBy`.
func (m Movie) Dispatch(ctx iris.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
# View Models
There should be the view models, the structure that the client will be able to see.
Example:
```go
import (
"github.com/kataras/iris/v12/_examples/mvc/repository/datamodels"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
type Movie struct {
datamodels.Movie
}
func (m Movie) IsValid() bool {
/* do some checks and return true if it's valid... */
return m.ID > 0
}
```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoretically, something like the following is permitted if it's really necessary;
```go
// Dispatch completes the `kataras/iris/mvc#Result` interface.
// Sends a `Movie` as a controlled http response.
// If its ID is zero or less then it returns a 404 not found error
// else it returns its json representation,
// (just like the controller's functions do for custom types by default).
//
// Don't overdo it, the application's logic should not be here.
// It's just one more step of validation before the response,
// simple checks can be added here.
//
// It's just a showcase,
// imagine the potentials this feature gives when designing a bigger application.
//
// This is called where the return value from a controller's method functions
// is type of `Movie`.
// For example the `controllers/movie_controller.go#GetBy`.
func (m Movie) Dispatch(ctx iris.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
```
However, we will use the "datamodels" as the only one models package because
Movie structure doesn't contain any sensitive data, clients are able to see all of its fields
and we don't need any extra functionality or validation inside it.

View File

@ -1,12 +1,12 @@
<!-- file: web/views/hello/index.html -->
<html>
<head>
<title>{{.Title}} - My App</title>
</head>
<body>
<p>{{.MyMessage}}</p>
</body>
<!-- file: web/views/hello/index.html -->
<html>
<head>
<title>{{.Title}} - My App</title>
</head>
<body>
<p>{{.MyMessage}}</p>
</body>
</html>

View File

@ -1,12 +1,12 @@
<!-- file: web/views/hello/name.html -->
<html>
<head>
<title>{{.}}' Portfolio - My App</title>
</head>
<body>
<h1>Hello {{.}}</h1>
</body>
<!-- file: web/views/hello/name.html -->
<html>
<head>
<title>{{.}}' Portfolio - My App</title>
</head>
<body>
<h1>Hello {{.}}</h1>
</body>
</html>