Upgrade to PRO for Only $50/Yearโ€”Limited-Time Offer! ๐Ÿ”ฅ

[EN] Golang Package Composition for Web Applica...

Avatar for mercari mercari PRO
September 30, 2017
150

[EN] Golang Package Composition for Web Application: The Case of Mercariย Kauru

Avatar for mercari

mercari PRO

September 30, 2017
Tweet

More Decks by mercari

Transcript

  1. Mercari Tech Conf 2017 Software Engineer Osamu Tonomori Golang Package

    Composition for Web Applications: The Case of Mercari Kauru
  2. Contents 1. About Me 2. About Mercari Kauru 3. About

    Package Composition 4. Usefulness of the Interface 5. Future Developments 6. Conclusion
  3. About Me โ€ข ไธปๆฃฎ ็† (Osamu Tonomori) Screen name: @osamingo

    โ€ข Souzoh Inc. Mercari Kauru team โ€ข Software Engineer Server-side, Gopher ส•โ—”ฯ–โ—”ส”
  4. I presented on how to use GAE at the TECH

    PLAY Conference 2017 โ€ข Google โ€œgae ๆ–ฐ่ฆ webโ€ to see the slides
  5. The three types of package composition โ€ข One Package When

    the repository itself is an individual package โ€ข Flat Packages Makes the purpose of each package clear, and separates accordingly. โ€ข Multiple Packages Separated by each design pattern, such as MVC, DDD etc.
  6. One Package โ€ข Simple is Best (เน‘โ€ขฬ€ใ…‚โ€ขฬ)ูˆโœง โ€ข Easy to

    get coverage โ€ข Good for things that can be done with a simple structure like libraries etc. . โ”œโ”€โ”€ ctx.go โ”œโ”€โ”€ ctx_test.go โ”œโ”€โ”€ debug.go โ”œโ”€โ”€ debug_test.go โ”œโ”€โ”€ error.go โ”œโ”€โ”€ error_test.go โ”œโ”€โ”€ handler.go โ”œโ”€โ”€ handler_func.go โ”œโ”€โ”€ handler_func_test.go โ”œโ”€โ”€ jsonrpc.go โ”œโ”€โ”€ jsonrpc_test.go โ”œโ”€โ”€ method.go โ”œโ”€โ”€ method_test.go โ”œโ”€โ”€ parse.go โ””โ”€โ”€ parse_test.go
  7. Difference between Flat Packages and Multiple Packages Feature A Feature

    B Feature C Feature A, Feature B, Feature C Feature A, Feature B, Feature C Feature A, Feature B, Feature C Flat Packages Categorized based on different features Multiple Packages Categorized based on design patterns
  8. Flat Packages โ€ข Similar to the standard Golang package structure

    โ€ข Even if you traverse the layers, it settles down at the second level โ€ข As roles are clear for each package, they are loose coupling โ€ข Good for Micro Services and Middleware etc. . โ”œโ”€โ”€ ctx โ”‚ โ”œโ”€โ”€ ctx.go โ”‚ โ””โ”€โ”€ ctx_test.go โ”œโ”€โ”€ debug โ”‚ โ”œโ”€โ”€ debug.go โ”‚ โ””โ”€โ”€ debug_test.go โ”œโ”€โ”€ error โ”‚ โ”œโ”€โ”€ error.go โ”‚ โ””โ”€โ”€ error_test.go โ”œโ”€โ”€ handler โ”‚ โ”œโ”€โ”€ handler.go โ”‚ โ”œโ”€โ”€ handler_func.go โ”‚ โ””โ”€โ”€ handler_func_test.go โ”œโ”€โ”€ jsonrpc.go โ”œโ”€โ”€ jsonrpc_test.go โ”œโ”€โ”€ parse โ”‚ โ”œโ”€โ”€ parse.go โ”‚ โ””โ”€โ”€ parse_test.go โ””โ”€โ”€ method โ”œโ”€โ”€ method.go โ””โ”€โ”€ method_test.go
  9. Multiple Packages โ€ข Package design that is closely linked to

    design patterns โ€ข Good for monolithic web services . โ”œโ”€โ”€ controller โ”‚ โ”œโ”€โ”€ debug โ”‚ โ”‚ โ”œโ”€โ”€ debug.go โ”‚ โ”‚ โ””โ”€โ”€ debug_test.go โ”‚ โ””โ”€โ”€ handler.go โ”œโ”€โ”€ jsonrpc.go โ”œโ”€โ”€ jsonrpc_test.go โ”œโ”€โ”€ model โ”‚ โ”œโ”€โ”€ ctx โ”‚ โ”‚ โ”œโ”€โ”€ ctx.go โ”‚ โ”‚ โ””โ”€โ”€ ctx_test.go โ”‚ โ”œโ”€โ”€ error โ”‚ โ”‚ โ”œโ”€โ”€ error.go โ”‚ โ”‚ โ””โ”€โ”€ error_test.go โ”‚ โ”œโ”€โ”€ marshal โ”‚ โ”‚ โ”œโ”€โ”€ unmarshal.go โ”‚ โ”‚ โ””โ”€โ”€ unmarshal_test.go โ”‚ โ””โ”€โ”€ method โ”‚ โ”œโ”€โ”€ method.go โ”‚ โ””โ”€โ”€ method_test.go โ””โ”€โ”€ view โ””โ”€โ”€ index.html.tpl
  10. Mercari Kauru uses Multiple Packages โ€ข A monolithic web service

    managed with one repository ใ€€As it uses GAE, it works well with a Project Based GOPATH. โ€ข Uses various design patterns that simplify the DDD ใ€€It is close to the composition of our previous Mercari Atte application and accounts for human resource mobility
  11. About the Google App Engine โ€ข Platform as a Service

    (PaaS) offered by GCP โ€ข Competitors include Heroku, Engine Yard etc.
  12. Repository Structure โ€ข Uses a gb that works well with

    GAE projects โ€ข $ gb vendor โ€ฆ ใ€€Dependent library management โ€ข $ gb gae โ€ฆ ใ€€gae-sdk wrap command group โ€ข Environment variables used for the test should be injected with direnv. . โ”œโ”€โ”€ Makefile โ”œโ”€โ”€ appengine # File that defines GAEโ”‚ โ”œโ”€โ”€ admin-api โ”‚ โ”œโ”€โ”€ api โ”‚ โ”œโ”€โ”€ cron.yaml โ”‚ โ”œโ”€โ”€ default โ”‚ โ”œโ”€โ”€ dispatch.yaml โ”‚ โ”œโ”€โ”€ index.yaml โ”‚ โ”œโ”€โ”€ management โ”‚ โ””โ”€โ”€ queue.yaml โ”œโ”€โ”€ cmd # Handmade convenient command group โ”œโ”€โ”€ docs # uml, sql etc. design docs โ”œโ”€โ”€ src โ”‚ โ””โ”€โ”€ kauru # Application itself โ”‚ โ”œโ”€โ”€ application โ”‚ โ”œโ”€โ”€ domain โ”‚ โ”œโ”€โ”€ infrastructure โ”‚ โ”œโ”€โ”€ interfaces โ”‚ โ””โ”€โ”€ library โ”œโ”€โ”€ test # Defines variables for test environment โ”‚ โ””โ”€โ”€ .envrc โ””โ”€โ”€ vendor # Dependent library management โ”œโ”€โ”€ manifest โ””โ”€โ”€ src
  13. โ€ข Made from 4 layers ใ€€Application ใ€€Domain ใ€€Infrastructure ใ€€Interface(s) โ€ข

    Existence of the library directory ใ€€Package group forms a complete unit Lightweight DDD . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”œโ”€โ”€ domain โ”œโ”€โ”€ infrastructure โ”œโ”€โ”€ interfaces โ””โ”€โ”€ library
  14. โ€ข Gathers up domain layer processes and provides them to

    the interface layer โ€ข Separates packages with same concept as Flat Packages โ€ข Used with internal packages and covers up the common logic Application Layer . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”‚ โ”œโ”€โ”€ audio โ”‚ โ”‚ โ””โ”€โ”€ audio.go โ”‚ โ”œโ”€โ”€ book โ”‚ โ”‚ โ””โ”€โ”€ book.go โ”‚ โ”œโ”€โ”€ exhibit โ”‚ โ”œโ”€โ”€ install โ”‚ โ”œโ”€โ”€ internal โ”‚ โ”œโ”€โ”€ product โ”‚ โ”œโ”€โ”€ ranking โ”‚ โ”œโ”€โ”€ search โ”‚ โ”œโ”€โ”€ stock โ”‚ โ”œโ”€โ”€ user โ”‚ โ””โ”€โ”€ visual โ”œโ”€โ”€ domain โ”œโ”€โ”€ infrastructure โ”œโ”€โ”€ interfaces โ””โ”€โ”€ library
  15. Domain Layer โ€ข The Domain layer does not exist in

    other layers โ€ข Provides Entity, Enum, Interface etc. . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”œโ”€โ”€ domain โ”‚ โ”œโ”€โ”€ bookmaster โ”‚ โ”‚ โ””โ”€โ”€ repository.go โ”‚ โ”œโ”€โ”€ bookmaster.go โ”‚ โ”œโ”€โ”€ configuration.go โ”‚ โ”œโ”€โ”€ domain.go โ”‚ โ”œโ”€โ”€ error.go โ”‚ โ”œโ”€โ”€ mercari.go โ”‚ โ”œโ”€โ”€ pager.go โ”‚ โ”œโ”€โ”€ product.go โ”‚ โ”œโ”€โ”€ time.go โ”‚ โ”œโ”€โ”€ user โ”‚ โ”‚ โ””โ”€โ”€ repository.go โ”‚ โ””โ”€โ”€ user.go โ”œโ”€โ”€ infrastructure โ”œโ”€โ”€ interfaces โ””โ”€โ”€ library
  16. Infrastructure Layer โ€ข Wrap the GAE library โ€ข Has basic

    processing capabilitiesใ€€ Logging ใ€€Validation Rule ใ€€Word Filtering ใ€€Value Get/Set in Context ใ€€Persistence . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”œโ”€โ”€ domain โ”œโ”€โ”€ infrastructure โ”‚ โ”œโ”€โ”€ backoff โ”‚ โ”‚ โ””โ”€โ”€ backoff.go โ”‚ โ”œโ”€โ”€ configuration โ”‚ โ”‚ โ””โ”€โ”€ configuration.go โ”‚ โ”œโ”€โ”€ crypto โ”‚ โ”œโ”€โ”€ ctx โ”‚ โ”œโ”€โ”€ identifier โ”‚ โ”œโ”€โ”€ log โ”‚ โ”œโ”€โ”€ persistence โ”‚ โ”œโ”€โ”€ queue โ”‚ โ”œโ”€โ”€ storage โ”‚ โ”œโ”€โ”€ tx โ”‚ โ”œโ”€โ”€ validation โ”‚ โ””โ”€โ”€ wordfilter โ”œโ”€โ”€ interfaces โ””โ”€โ”€ library
  17. Interface Layer โ€ข JSON-RPCโ€™s Handler group ใ€€Search.FindProduct etc. โ€ข Original

    Error definition ใ€€-32044: Not Found etc. โ€ข Interceptor group ใ€€Packs a lot into context ใ€€Action Logger etc. โ€ข Other ใ€€Startup processing ใ€€Obtains movement condition . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”œโ”€โ”€ domain โ”œโ”€โ”€ infrastructure โ”œโ”€โ”€ interfaces โ”‚ โ”œโ”€โ”€ api โ”‚ โ”‚ โ””โ”€โ”€ search โ”‚ โ”‚ โ””โ”€โ”€ find_product.go โ”‚ โ”œโ”€โ”€ error.go โ”‚ โ”œโ”€โ”€ interceptor โ”‚ โ”‚ โ””โ”€โ”€ recovery.go โ”‚ โ”œโ”€โ”€ interfaces.go โ”‚ โ”œโ”€โ”€ management โ”‚ โ”‚ โ””โ”€โ”€ cron โ”‚ โ”‚ โ””โ”€โ”€ backup_datastore.go โ”‚ โ”œโ”€โ”€ stats.go โ”‚ โ””โ”€โ”€ warmup.go โ””โ”€โ”€ library
  18. Library Directory โ€ข Package group that is not dependent on

    the application โ€ข Many have been made into OSS packages ใ€€github.com ใ€€ใ€€/osamingo - 5 packages ใ€€ใ€€/tenntenn - 3 packages . โ””โ”€โ”€ kauru โ”œโ”€โ”€ application โ”œโ”€โ”€ domain โ”œโ”€โ”€ infrastructure โ”œโ”€โ”€ interfaces โ””โ”€โ”€ library โ”œโ”€โ”€ device โ”‚ โ””โ”€โ”€ device.go โ”œโ”€โ”€ errutil โ”‚ โ””โ”€โ”€ temporary.go โ”œโ”€โ”€ io โ””โ”€โ”€ strrecord
  19. Points to Be Careful of โ€ข Each underlying layer is

    composed of flat packages ใ€€The most important thing is cutting the packages appropriately โ€ข Even within the layer, we need to protect the domain layer above all else ใ€€If the Domain fails, weโ€™ll be in cycle import hell and it will become weak to changes. โ€ข If youโ€™re lost, choose the most simple path ใ€€I like the quote โ€œKeep It Simple Stupidโ€
  20. Background โ€ข The design pattern used for Multiple Packages often

    waits for object oriented movement โ€ข With Golang, we can use the interface and fillers to expect object oriented movement.
  21. So does Golang even have objects? โ€ข โ€œIs Golang an

    object oriented language?โ€ ใ€€http://postd.cc/is-go-object-oriented/ ใ€€This post from @spf13 is really helpful. โ€ข What does โ€œobject orientedโ€ here refer to? a. It doesnโ€™t refer to the program as code and data, but uses the concept of an โ€œobjectโ€ to integrate the two b. An object is an abstract data type that has a condition (data) and a behavior (code)
  22. type ( Duck interface { Waddler Quacker } Waddler interface

    { Waddle() (lat, lng float64) } Quacker interface { Quack() string } ) โ€ข Defined method list โ€ข An interface without a method is interface{} โ€ข All defined types fulfill interface{} How to Define the Interface
  23. type Osamingo struct { Lat float64 Lng float64 } func

    (o *Osamingo) Waddle() (float64, float64) { return o.Lat, o.Lng } func (o *Osamingo) Quack() string { return "Hey" } func main() { var o interface{} = &Osamingo{ Lat: 35.662056, Lng: 139.728451, } switch o.(type) { case Duck: fmt.Println("Hi, duck!") } } How to Implement the Interface โ€ข Similar to Duck Typing โ€ข If you implement Waddler and Quacker, it will become Duck.
  24. An Example of the Flow of Registering User Info in

    the Database โ€ข An example of what we are actually doing with Mercari Kauru. โ€ข Will introduce with the following steps a. Entity interface design b. User entity implementation c. UserRepository interface design d. UserRepository implementation e. Cloud Datastore library function wrap
  25. About Cloud Datastore โ€ข A fully-managed NoSQL service offered by

    GCP GCP โ€ข Used by Pokรฉmon GO etc. โ€ข โ€œGoogle Cloud Datastore Inside-Outโ€ ใ€€https://www.slideshare.net/enakai/google-cloud-datastore-insideout
  26. // Domain layer - domain/domain.go type ( EntityBehavior interface {

    Identifier datastore.PropertyLoadSaver } Identifier interface { GetID() string SetID(string) } UpdateTimestamper interface { GetUpdatedAt() time.Time SetUpdatedAt(time.Time) } ) โ€ข EntityBehavior ใ€€Condition for storing in the database โ€ข Identifier Getter/Setter for the ID datastore. โ€ข PropertyLoadSaver ใ€€Manages load, save from the datastore โ€ข CreateTimestamper ใ€€Manages CreatedAt โ€ข UpdateTimestamper ใ€€Manages UpdatedAt Entity Interface
  27. User Entity Architecture โ€ข Construct User Entity ใ€€Nothing complicated //

    Domain layer - domain/user.go type User struct { ID string `json:"id" datastore:"-" validate:โ€requiredโ€` ConnectedAt time.Time `json:"connected_at" datastore:"connected_at" validate:โ€requiredโ€` }
  28. User Entity Implementation // Domain layer - domain/user.go func (u

    *User) GetID() string { return u.ID } func (u *User) SetID(id string) { u.ID = id } func (u *User) Load(p []datastore.Property) error { return Load(u, p) } func (u *User) Save() ([]datastore.Property, error) { return Save(u) } โ€ข Implementing EntityBehavior for the user โ€ข For Load and Save, prepare a common function within the entity
  29. Entityโ€™s Load Function // Domain layer - domain/domain.go func Load(dst

    interface{}, p []datastore.Property) error { err := datastore.LoadStruct(dst, p) if err != nil { return err } return LoadTimestamps(dst, p) } func LoadTimestamps(i interface{}, p []datastore.Property) error { ut, isUT := i.(UpdateTimestamper) if !isUT { return nil } for i := range p { if p[i].Name == "updated_at" { ut.SetUpdatedAt(p[i].Value.(time.Time)) } } return nil } โ€ข Uses App Engine libraryโ€™s LoadStruct โ€ข If xTimestamper is implemented, it will be loaded
  30. // Domain layer - domain/domain.go func Save(src interface{}) ([]datastore.Property, error)

    { p, err := datastore.SaveStruct(src) if err != nil { return nil, err } return append(p, SaveTimestamps(src)...), nil } func SaveTimestamps(i interface{}) []datastore.Property { p := make([]datastore.Property, 0, 1) if ut, ok := i.(UpdateTimestamper); ok { ut.SetUpdatedAt(time.Now()) return append(nil, datastore.Property{ Name: "updated_at", Value: ut.GetUpdatedAt(), }) } return nil } โ€ข Uses the App Engine libraryโ€™s savestruct โ€ข If xTimestamper is implemented, a time will be set Entityโ€™s Save Function
  31. Repositoryโ€™s Interface // Domain layer - domain/user/user.go type repository interface

    { Get(c context.Context, id int64) (*domain.User, error) Save(c context.Context, u *domain.User) error } var Repository repository โ€ข Java Interface definition โ€ข The Infrastructure layerโ€™s persistence package is in charge of implementation
  32. Repository Implementation // Infrastructure layer - // infrastructure/persistence/user_repository.go type UserRepository

    datastoreRepository func NewUserRepository() *UserRepository { return &UserRepository{ kind: "User", } } func (r *UserRepository) Get(c context.Context, id int64) (*domain.User, error) { u := &domain.User{} u.SetID(id) return u, get(c, r.kind, u) } func (r *UserRepository) Save(c context.Context, u *domain.User) error { return put(c, r.kind, u) } โ€ข DI the domain layer from the Infrastructure layer โ€ข For get and put, prepare a common function within the repository.
  33. Datastore Function Wrap // Infrastructure layer - // infrastructure/persistence/persistence.go func

    get(c context.Context, kind string, dst domain.EntityBehavior) error { key := datastore.NewKey(c, kind, dst.GetID(), 0, nil) return datastore.Get(c, key, dst) } func put(c context.Context, kind string, src domain.EntityBehavior) error { if err := validation.Check(src); err != nil { return err } key :-= datastore.NewKey(c, kind, src.GetID(), 0, nil) _, err := datastore.Put(c, key, src) return err } โ€ข For each parameter, set a kind and EntityBehavior โ€ข Use the Get, Put in the app engine library
  34. Unique Points โ€ข Separating use of an interface as an

    entity and as a repository ใ€€For the Entity, expect an action ใ€€For the Repository, expect DI โ€ข Wrap the App Engine library and use it ใ€€Use the interface as an entity ใ€€
  35. Moving Towards Clearer DDD โ€ข As the application grows, the

    logic expands into the application layer and enlarges internal packages more than required. => In order to prioritize development speed, I tried my best to implement DRY => However, it is true that DDD and DRY are not compatible => Remove the reference from the interface, Application layers to the infrastructure layer.
  36. Streamlining the Initialization Process โ€ข As an application grows, the

    initialization processing becomes split up into interface layer and infrastructure layer parts. => Register JSON-RPC method in the interface layer => DI of domain repository in the infrastructure layer => Youโ€™re really not supposed to initialize on each layerโ€ฆ (ใƒฝยดฯ‰`) => Start an application and streamline initialization processing within the main function.
  37. Knowledge Extraction โ€ข Of the interfaces introduced, identifier and TimeStamper

    are some of the more commonly used => Extracting the Datastore library as a loosely wrapped construct makes it easier to generalize and maintain => Most new services within Souzoh use GAE and Datastore, which helps improve the synergy => Will continue to create libraries for sharing, adding test functionality as well
  38. Conclusion โ€ข It is best to consider the programming language

    you are going to use before designing. Dev will be smoother if you consider what each language is best at beforehand โ€ข It is hard to be perfect from the start. However, it is possible to make it adaptable to change. ใ€€It is not good to create a condition where special cases are not accepted right from the start.