forked from katzien/go-structure-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Kat
committed
Jun 2, 2018
0 parents
commit 35dbc16
Showing
93 changed files
with
3,991 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 Kat | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[[constraint]] | ||
name = "github.com/julienschmidt/httprouter" | ||
version = "1.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/katzien/structure-examples/domain-driven/storage" | ||
"github.com/katzien/structure-examples/domain-driven/adding" | ||
"github.com/katzien/structure-examples/domain-driven/reviewing" | ||
) | ||
|
||
func main() { | ||
|
||
// error handling omitted for simplicity | ||
beersStorage, _ := storage.NewJSONBeerStorage() | ||
reviewsStorage, _ := storage.NewJSONReviewStorage() | ||
|
||
// create the available services | ||
adder := adding.NewService(beersStorage) | ||
reviewer := reviewing.NewService(reviewsStorage) | ||
|
||
// add some sample data | ||
adder.AddSampleBeers() | ||
reviewer.AddSampleReviews() | ||
|
||
fmt.Println("Finished adding sample data.") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/julienschmidt/httprouter" | ||
"github.com/katzien/structure-examples/domain-driven/beers" | ||
"github.com/katzien/structure-examples/domain-driven/reviews" | ||
"github.com/katzien/structure-examples/domain-driven/storage" | ||
"github.com/katzien/structure-examples/domain-driven/adding" | ||
"github.com/katzien/structure-examples/domain-driven/reviewing" | ||
"github.com/katzien/structure-examples/domain-driven/listing" | ||
) | ||
|
||
func main() { | ||
|
||
// set up storage | ||
storageType := storage.InMemory // this could be a flag; hardcoded here for simplicity | ||
|
||
var beersStorage beers.Repository | ||
var reviewsStorage reviews.Repository | ||
|
||
switch storageType { | ||
case storage.InMemory: | ||
beersStorage = new(storage.MemoryBeerStorage) | ||
reviewsStorage = new(storage.MemoryReviewStorage) | ||
case storage.JSONFiles: | ||
// error handling omitted for simplicity | ||
beersStorage, _ = storage.NewJSONBeerStorage(); | ||
reviewsStorage, _ = storage.NewJSONReviewStorage(); | ||
} | ||
|
||
// create the available services | ||
adder := adding.NewService(beersStorage) | ||
reviewer := reviewing.NewService(reviewsStorage) | ||
lister := listing.NewService(beersStorage, reviewsStorage) | ||
|
||
// add some sample data | ||
adder.AddSampleBeers() | ||
reviewer.AddSampleReviews() | ||
|
||
// set up the HTTP server | ||
router := httprouter.New() | ||
|
||
router.GET("/beers", listing.MakeGetBeersEndpoint(lister)) | ||
router.GET("/beers/:id", listing.MakeGetBeerEndpoint(lister)) | ||
router.GET("/beers/:id/reviews", listing.MakeGetBeerReviewsEndpoint(lister)) | ||
|
||
router.POST("/beers", adding.MakeAddBeerEndpoint(adder)) | ||
router.POST("/beers/:id/reviews", reviewing.MakeAddBeerReviewEndpoint(reviewer)) | ||
|
||
fmt.Println("The beer server is on tap now: http://localhost:8080") | ||
log.Fatal(http.ListenAndServe(":8080", router)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package adding | ||
|
||
import ( | ||
"github.com/julienschmidt/httprouter" | ||
"net/http" | ||
"encoding/json" | ||
"github.com/katzien/structure-examples/domain-driven/beers" | ||
) | ||
|
||
// MakeAddBeerEndpoint creates a handler for POST /beers requests | ||
func MakeAddBeerEndpoint(s Service) func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||
decoder := json.NewDecoder(r.Body) | ||
|
||
var newBeer beers.Beer | ||
err := decoder.Decode(&newBeer) | ||
if err != nil { | ||
http.Error(w, "Bad beer - this will be a HTTP status code soon!", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
s.AddBeer(newBeer) | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode("New beer added.") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package adding | ||
|
||
import ( | ||
"github.com/katzien/structure-examples/domain-driven/beers" | ||
) | ||
|
||
// Service provides beer or review adding operations | ||
type Service interface { | ||
AddBeer(b ...beers.Beer) | ||
AddSampleBeers() | ||
} | ||
|
||
type service struct { | ||
bR beers.Repository | ||
} | ||
|
||
// NewService creates an adding service with the necessary dependencies | ||
func NewService(bR beers.Repository) Service { | ||
return &service{bR} | ||
} | ||
|
||
// AddBeer adds the given beer(s) to the database | ||
func (s *service) AddBeer(b ...beers.Beer) { | ||
for _, beer := range b { | ||
_ = s.bR.Add(beer) // error handling omitted for simplicity | ||
} | ||
} | ||
|
||
// AddSampleBeers adds some sample beers to the database | ||
func (s *service) AddSampleBeers() { | ||
for _, b := range beers.DefaultBeers { | ||
_ = s.bR.Add(b) // error handling omitted for simplicity | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package beers | ||
|
||
import ( | ||
"time" | ||
"errors" | ||
) | ||
|
||
// Beer defines the properties of a reviewable beer | ||
type Beer struct { | ||
ID int `json:"id"` | ||
Name string `json:"name"` | ||
Brewery string `json:"brewery"` | ||
Abv float32 `json:"abv"` | ||
ShortDesc string `json:"short_description"` | ||
Created time.Time `json:"created"` | ||
} | ||
|
||
// ErrUnknown is used when a beer could not be found. | ||
var ErrUnknown = errors.New("unknown beer") | ||
var ErrDuplicate = errors.New("beer already exists") | ||
|
||
// Repository provides access to the list of beers. | ||
type Repository interface { | ||
GetAll() []Beer | ||
Get(id int) (Beer, error) | ||
Add(Beer) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package beers | ||
|
||
import "time" | ||
|
||
var DefaultBeers = []Beer{ | ||
{ | ||
ID: 1, | ||
Name: "Pliny the Elder", | ||
Brewery: "Russian River Brewing Company", | ||
Abv: 8, | ||
ShortDesc: "Pliny the Elder is brewed with Amarillo, " + | ||
"Centennial, CTZ, and Simcoe hops. It is well-balanced with " + | ||
"malt, hops, and alcohol, slightly bitter with a fresh hop " + | ||
"aroma of floral, citrus, and pine.", | ||
Created: time.Date(2017, time.October, 24, 22, 6, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 2, | ||
Name: "Oatmeal Stout", | ||
Brewery: "Samuel Smith", | ||
Abv: 5, | ||
ShortDesc: "Brewed with well water (the original well at the " + | ||
"Old Brewery, sunk in 1758, is still in use, with the hard well " + | ||
"water being drawn from 85 feet underground); fermented in " + | ||
"‘stone Yorkshire squares’ to create an almost opaque, " + | ||
"wonderfully silky and smooth textured ale with a complex " + | ||
"medium dry palate and bittersweet finish.", | ||
Created: time.Date(2017, time.October, 24, 22, 12, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 3, | ||
Name: "Märzen", | ||
Brewery: "Schlenkerla", | ||
Abv: 5, | ||
ShortDesc: "Bamberg's speciality, a dark, bottom fermented " + | ||
"smokebeer, brewed with Original Schlenkerla Smokemalt from " + | ||
"the Schlenkerla maltings and tapped according to old tradition " + | ||
"directly from the gravity-fed oakwood cask in the historical " + | ||
"brewery tavern.", | ||
Created: time.Date(2017, time.October, 24, 22, 17, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 4, | ||
Name: "Duvel", | ||
Brewery: "Duvel Moortgat", | ||
Abv: 9, | ||
ShortDesc: "A Duvel is still seen as the reference among strong " + | ||
"golden ales. Its bouquet is lively and tickles the nose with an " + | ||
"element of citrus which even tends towards grapefruit thanks to " + | ||
"the use of only the highest-quality hop varieties.", | ||
Created: time.Date(2017, time.October, 24, 22, 24, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 5, | ||
Name: "Negra", | ||
Brewery: "Modelo", | ||
Abv: 5, | ||
ShortDesc: "Brewed longer to enhance the flavors, this Munich " + | ||
"Dunkel-style Lager gives way to a rich flavor and remarkably " + | ||
"smooth taste.", | ||
Created: time.Date(2017, time.October, 24, 22, 27, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 6, | ||
Name: "Guinness Draught", | ||
Brewery: "Guinness Ltd.", | ||
Abv: 4, | ||
ShortDesc: "Pours dark brown, almost black with solid lasting light brown head. " + | ||
"Aroma of bitter cocoa, light coffee and roasted malt. " + | ||
"Body is light sweet, medium bitter. " + | ||
"Body is light to medium, texture almost thin and carbonation average. " + | ||
"Finish is medium bitter cocoa with more pronounced roast flavor. Smooth drinker.", | ||
Created: time.Date(2017, time.October, 24, 22, 27, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 7, | ||
Name: "XX Lager", | ||
Brewery: "Cuahutemoc Moctezuma", | ||
Abv: 4.2, | ||
ShortDesc: "A crisp, refreshing, light-bodied malt-flavored beer with a well-balanced finish. " + | ||
"A Lager that drinks like a Pilsner. A liquid embodiment of living life to the fullest. " + | ||
"A beverage made from pure spring water and the choicest hops. A beer with such good taste, it’s chosen you to drink it.", | ||
Created: time.Date(2017, time.October, 28, 15, 02, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 8, | ||
Name: "Tecate", | ||
Brewery: "Cuahutemoc Moctezuma", | ||
Abv: 5, | ||
ShortDesc: "Very smooth, medium bodied brew. Malt sweetness is thin, and can be likened to diluted sugar water. " + | ||
"Touch of fructose-like sweetness. Light citric hop flavours gently prick the palate with tea-like notes that follow and fade quickly. " + | ||
"Finishes a bit dry with husk tannins and a pasty mouthfeel.", | ||
Created: time.Date(2017, time.October, 28, 15, 07, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 9, | ||
Name: "Sol", | ||
Brewery: "Cuahutemoc Moctezuma", | ||
Abv: 5, | ||
ShortDesc: "While Corona wins the marketing wars in the U.S., Sol is the winning brand in much of Mexico, despite not being a standout in any respect. " + | ||
"You see the logo plastered everywhere and it’s seemingly on every restaurant and bar menu. Like Corona, it’s simple and inoffensive, " + | ||
"but still slightly more flavorful than your typical American macrobrew. At its best ice cold, and progressively worse as it gets warmer.", | ||
Created: time.Date(2017, time.October, 28, 15, 12, 0, 0, time.UTC), | ||
}, | ||
{ | ||
ID: 10, | ||
Name: "Corona", | ||
Brewery: "Cuahutemoc Moctezuma", | ||
Abv: 5, | ||
ShortDesc: "One of the five best-selling beers in the world, but it usually tastes better in Mexico, " + | ||
"where the bottles don’t have so much time in transit and on shelves. (Sunlight coming through clear bottles is never a good thing for beer.) " + | ||
"This is the typical “drink all afternoon” beer, working well on its own or with a plate of tacos. Refreshing with a lime.", | ||
Created: time.Date(2017, time.October, 28, 15, 14, 0, 0, time.UTC), | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package listing | ||
|
||
import ( | ||
"github.com/julienschmidt/httprouter" | ||
"net/http" | ||
"encoding/json" | ||
"strconv" | ||
"fmt" | ||
"github.com/katzien/structure-examples/domain-driven/beers" | ||
) | ||
|
||
type Handler func(http.ResponseWriter, *http.Request, httprouter.Params) | ||
|
||
// MakeAddBeerEndpoint creates a handler for GET /beers requests | ||
func MakeGetBeersEndpoint(s Service) func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||
w.Header().Set("Content-Type", "application/json") | ||
list := s.GetBeers() | ||
json.NewEncoder(w).Encode(list) | ||
} | ||
} | ||
|
||
// MakeAddBeeEndpoint creates a handler for GET /beers/:id requests | ||
func MakeGetBeerEndpoint(s Service) func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { | ||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { | ||
ID, err := strconv.Atoi(p.ByName("id")) | ||
if err != nil { | ||
http.Error(w, fmt.Sprintf("%s is not a valid beer ID, it must be a number.", p.ByName("id")), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
beer, err := s.GetBeer(ID) | ||
if err == beers.ErrUnknown { | ||
http.Error(w, "The beer you requested does not exist.", http.StatusNotFound) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode(beer) | ||
} | ||
} | ||
|
||
// MakeGetBeerReviewsEndpoint creates a handler for GET /beers/:id/reviews requests | ||
func MakeGetBeerReviewsEndpoint(s Service) func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { | ||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { | ||
ID, err := strconv.Atoi(p.ByName("id")) | ||
if err != nil { | ||
http.Error(w, fmt.Sprintf("%s is not a valid beer ID, it must be a number.", p.ByName("id")), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
reviews, err := s.GetBeerReviews(ID) | ||
if err != nil { | ||
http.Error(w, fmt.Sprintf("%s is not a valid beer ID.", p.ByName("id")), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode(reviews) | ||
} | ||
} |
Oops, something went wrong.