API
项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ tree . ├── cmd │ └── bookstore │ └── main.go ├── go.mod ├── internal │ └── store │ └── memstore.go ├── server │ ├── middleware │ │ └── middleware.go │ └── server.go └── store ├── factory │ └── factory.go └── store.go
逻辑结构
具体实现 store
定义 Domain 和 Action
store/store.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package storetype Book struct { Id string `json:"id"` Name string `json:"name"` Authors []string `json:"authors"` Press string `json:"press"` } type Store interface { Create(*Book) error Update(*Book) error Get(string ) (Book, error ) GetAll() ([]Book, error ) Delete(string ) error }
Go 风格的工厂模式 - 注册模式
store/factory/factory.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package factoryimport ( "fmt" "github.com/zhongmingmao/bookstore/store" "sync" ) var ( providersMu sync.RWMutex providers = make (map [string ]store.Store) ) func Register (name string , p store.Store) { providersMu.Lock() defer providersMu.Unlock() if p == nil { panic ("store: Register provider is nil" ) } if _, dup := providers[name]; dup { panic ("store: Register called twice for provider " + name) } providers[name] = p } func New (providerName string ) (store.Store, error ) { providersMu.RLock() p, ok := providers[providerName] providersMu.RUnlock() if !ok { return nil , fmt.Errorf("store: unknown provider %q" , providerName) } return p, nil }
internal/store/memstore.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package storeimport ( "github.com/zhongmingmao/bookstore/store" "github.com/zhongmingmao/bookstore/store/factory" "sync" ) func init () { factory.Register("mem" , &MemStore{ books: make (map [string ]*store.Book), }) } type MemStore struct { sync.RWMutex books map [string ]*store.Book }
server
middleware 处理一些通用逻辑,类似于 Java 的切面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package middlewareimport ( "log" "mime" "net/http" ) func Logging (next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { log.Printf("recv a %s request from %s\n" , r.Method, r.RemoteAddr) next.ServeHTTP(w, r) }) } func Validating (next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { contentType := r.Header.Get("Content-Type" ) mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if mediaType != "application/json" { http.Error(w, "Content-Type must be application/json" , http.StatusBadRequest) return } next.ServeHTTP(w, r) }) }
server/server.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package serverimport ( "github.com/gorilla/mux" "github.com/zhongmingmao/bookstore/server/middleware" "github.com/zhongmingmao/bookstore/store" "net/http" "time" ) type BookStoreServer struct { s store.Store srv *http.Server } func NewBookStoreServer (addr string , s store.Store) *BookStoreServer { srv := &BookStoreServer{ s: s, srv: &http.Server{ Addr: addr, }, } router := mux.NewRouter() router.HandleFunc("/book" , srv.createBookHandler).Methods("POST" ) router.HandleFunc("/book/{id}" , srv.updateBookHandler).Methods("POST" ) router.HandleFunc("/book/{id}" , srv.getBookHandler).Methods("GET" ) router.HandleFunc("/book" , srv.getAllBookHandler).Methods("GET" ) router.HandleFunc("/book/{id}" , srv.deleteBookHandler).Methods("DELETE" ) srv.srv.Handler = middleware.Logging(middleware.Validating(router)) return srv } func (bs *BookStoreServer) ListenAndServe() (<-chan error , error ) { var err error errChan := make (chan error ) go func () { err = bs.srv.ListenAndServe() errChan <- err }() select { case err := <-errChan: return nil , err case <-time.After(time.Second): return errChan, nil } } func (bs *BookStoreServer) Shutdown(ctx Context) error { return bs.srv.Shutdown(ctx) }
main main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func main () { s, err := factory.New("mem" ) if err != nil { panic (err) } srv := server.NewBookStoreServer(":8081" , s) errChan, err := srv.ListenAndServe() if err != nil { log.Println("web server start failed:" , err) return } log.Println("web server start success" ) c := make (chan os.Signal, 1 ) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) select { case err := <-errChan: log.Println("web server run failed:" , err) return case <-c: log.Println("web server shutdown" ) ctx, cf := context.WithTimeout(context.Background(), time.Second) defer cf() err = srv.Shutdown(ctx) } if err != nil { log.Println("web server shutdown failed:" , err) return } log.Println("web server shutdown success" ) }