ããã€ããµãŒãã¹ã¯ãå€§èŠæš¡ãããžã§ã¯ãã®ç¹å®ã®ã¢ãžã¥ãŒã«éã®ãããã·ã§ããäžèŠãããšãäžæ©ã§ãããç ç©¶ããããéèŠãªããšã«åãæããããšãã§ããŸããããããåãå§ããŠãç§ã¯èªåãééã£ãŠããããšã«æ°ã¥ããŸããããã®ãµãŒãã¹ã¯ãMVPããã¹ãããã¿ã¹ã¯ã䜿çšããŠã6ãæåã«æ°é±éã§äœæãããŸããããã®éãã£ãšã圌ã¯ä»äºãæåŠããŸããã圌ã¯ã€ãã³ããããŒã¿ã倱ã£ãããæžãçŽãããããŸããããããžã§ã¯ãã¯ããŒã ããããŒã ãžãšæãåºãããŸããããªããªãããã®äœæè ã§ããã誰ããããããããããªãã£ãããã§ããä»ã圌ããããã®ããã«å¥ã®ããã°ã©ããŒãæ¢ããŠããçç±ãæããã«ãªããŸããã
ãç§ã®ããµãŒãã¹ã¯ã貧匱ãªã¢ãŒããã¯ãã£ãšæ¬è³ªçã«æ£ãããªãèšèšã®äŸã§ããç§ãã¡ã¯çãããªãããããè¡ãããšãã§ããªãããšãçè§£ããŠããŸãããããããªãããã§ã¯ãªãã®ãããããã©ã®ãããªçµæã«ã€ãªããã®ãããããŠã©ã®ããã«ãã¹ãŠãä¿®æ£ããããšããã®ããç§ã¯ããªãã«è©±ããŸãã
æªãã¢ãŒããã¯ãã£ãéªéã«ãªãæ¹æ³
å žåçãªè©±ïŒ
- MVPãäœæããŸãã
- ãã®äžã§ä»®èª¬ããã¹ãããŸãã
- , MVP;
- ...;
- PROFIT.
ããããããã¯ã§ããŸããïŒç§ãã¡å šå¡ãçè§£ããŠããŸãïŒã
ã·ã¹ãã ãæ¥ãã§æ§ç¯ãããŠããå Žåã補åã®æ°ããããŒãžã§ã³ããªãªãŒã¹ãç¶ããå¯äžã®æ¹æ³ã¯ãã¹ã¿ããããèšããŸãããããšã§ããåœåãéçºè ã¯100ïŒ ã«è¿ãçç£æ§ã瀺ããŠããŸãããæåã®ãçã®ã補åãæ©èœãäŸåé¢ä¿ã§å€§ãããªãããããšããããçè§£ããã®ã«ãŸããŸãæéãããããŸãã
æ°ããããŒãžã§ã³ããšã«ãéçºè ã®çç£æ§ã¯äœäžããŸããã³ãŒãã®ã¯ãªãŒã³ãããã¶ã€ã³ãã¢ãŒããã¯ãã£ã«ã€ããŠã¯èª°ãèããŠããŸããããã®çµæãã³ãŒãè¡ã®äŸ¡æ Œã40åã«ãªãå¯èœæ§ããããŸãã
ãããã®ããã»ã¹ã¯ãRobertMartinã®ã°ã©ãã§ã¯ã£ãããšèŠãããšãã§ããŸããéçºè ã®ã¹ã¿ãããããŒãžã§ã³ããšã«å¢å ããŠãããšããäºå®ã«ããããããã補åã®æé·ã®ãã€ããã¯ã¹ã¯æžéããŠããã ãã§ããã³ã¹ãã¯å¢å ããåçã¯æžå°ããŠãããããã¯ãã§ã«ã¹ã¿ããã®åæžã«ã€ãªãã£ãŠããŸãã
ã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã®èª²é¡
ã¢ããªã±ãŒã·ã§ã³ãã©ã®ããã«èšèšããã³äœæãããŠãããã¯ãããžãã¹ã«ãšã£ãŠéèŠã§ã¯ãããŸããã補åããŠãŒã¶ãŒãæãããã«åäœããåçæ§ãé«ãããšã¯ãããžãã¹ã«ãšã£ãŠéèŠã§ããããããæã ïŒæã ã§ã¯ãããŸããããé »ç¹ã«ïŒãããžãã¹ã¯ãœãªã¥ãŒã·ã§ã³ãšèŠä»¶ã倿ŽããŸããæ§é ã貧匱ãªãããæ°ããèŠä»¶ãžã®é©å¿ã補åã®å€æŽãããã³æ°ããæ©èœã®è¿œå ã¯å°é£ã§ãã
é©åã«èšèšãããã·ã¹ãã ã¯ãç®çã®åäœã«ç°¡åã«é©åã§ããŸããç¹°ãè¿ãã«ãªããŸãããRobert Martinã¯ãåäœã¯äºæ¬¡çãªãã®ã§ãããã·ã¹ãã ãé©åã«èšèšãããŠããã°ãã€ã§ãä¿®æ£ã§ãããšèããŠããŸãã
ã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã¯ããããžã§ã¯ãå ã®ã¬ã€ã€ãŒéã®éä¿¡ãä¿é²ããŸããäžå¿ãšãªãã®ã¯ãé©çšãããã¿ã¹ã¯ãåŠçãããã¹ãŠã®ãšã³ãã£ãã£ãåããããžãã¹ããžãã¯ã§ãã
- ãã¹ãŠã®å€å±€ã¯ãå€çãšéä¿¡ããããã®ã¢ããã¿ãŒã§ãã
- å€çã®èŠçŽ ããããžã§ã¯ãã®äžå¿éšåã«æµžéããŠã¯ãªããŸããã
ããžãã¹ããžãã¯ã¯ããã¹ã¯ãããã¢ããªã±ãŒã·ã§ã³ãWebãµãŒããŒããã€ã¯ãã·ã¹ãã ãªã©ã誰ã§ããããæ°ã«ããŸããããã©ãã«ãã«äŸåããã¹ãã§ã¯ãããŸããã圌女ã¯ç¹å®ã®ã¿ã¹ã¯ãå®è¡ããå¿ èŠããããŸããããŒã¿ããŒã¹ããã¹ã¯ããããªã©ããã®ä»ã¯ãã¹ãŠè©³çްã§ãã
ã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã«ãããç¬ç«ããã·ã¹ãã ãåŸãããŸããããšãã°ãããŒã¿ããŒã¹ããã¬ãŒã ã¯ãŒã¯ã®ããŒãžã§ã³ã«äŸåããŸãããããžãã¹ããžãã¯ã®å éšã³ã³ããŒãã³ãã倿ŽããããšãªãããµãŒããŒã®ããŒãºã«åãããŠãã¹ã¯ãããã¢ããªã±ãŒã·ã§ã³ã眮ãæããããšãã§ããŸãããããããžãã¹ããžãã¯ã®äŸ¡å€ã§ãã
ã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã¯ããããžã§ã¯ãã®èªèã®è€éãããµããŒãã³ã¹ããåæžããããã°ã©ããŒã®éçºãšãããªãã¡ã³ããã³ã¹ãç°¡çŽ åããŸãã
ãæªããã¢ãŒããã¯ãã£ãç¹å®ããæ¹æ³
ããã°ã©ãã³ã°ã«ã¯ãæªããã¢ãŒããã¯ãã£ã®æŠå¿µã¯ãããŸããã貧匱ãªã¢ãŒããã¯ãã£ã®åºæºããããŸãïŒåæ§ãäžåã匷éããé床ã®åçŸæ§ãããšãã°ããããã¯ç§ã®ãã€ã¯ããµãŒãã¹ã®ã¢ãŒããã¯ãã£ãæªãããšãçè§£ããããã«äœ¿çšããåºæºã§ãã
åæ§ãå°ããªå€æŽã§ãã·ã¹ãã ã察å¿ã§ããªããããã·ã¹ãã å šäœã«ãã¡ãŒãžãäžããã«ãããžã§ã¯ãã®äžéšã倿Žããããšãå°é£ã«ãªããšãã·ã¹ãã ã¯å åºã«ãªããŸããããšãã°ã1ã€ã®æ§é ããããžã§ã¯ãã®è€æ°ã®ã¬ã€ã€ãŒã§äžåºŠã«äœ¿çšãããå Žåããã®å°ããªå€æŽã«ãã£ãŠãããžã§ã¯ãå šäœã§äžåºŠã«åé¡ãçºçããŸãã
ãã®åé¡ã¯ãåã¬ã€ã€ãŒã§å€æããããšã§è§£æ±ºãããŸããåã¬ã€ã€ãŒãå€éšãªããžã§ã¯ããã倿ãããããšã«ãã£ãŠååŸããããªããžã§ã¯ãã®ã¿ãæäœããå Žåãã¬ã€ã€ãŒã¯å®å šã«ç¬ç«ããäžåæ§ã«ãªããŸã
..ãã·ã¹ãã ãåå©çšå¯èœãªã¢ãžã¥ãŒã«ãžã®åé¢ãäžååïŒãŸãã¯æ¬ åŠïŒã§æ§ç¯ããããšããåºå®ã·ã¹ãã ã¯ãªãã¡ã¯ã¿ãªã³ã°ãå°é£ã§ãã
ããšãã°ãããŒã¿ããŒã¹ã«é¢ããæ å ±ãããžãã¹ããžãã¯ã®é åã«å ¥ããšãããŒã¿ããŒã¹ãå¥ã®ããŒã¿ããŒã¹ã«çœ®ãæãããšããã¹ãŠã®ããžãã¹ããžãã¯ããªãã¡ã¯ã¿ãªã³ã°ãããŸãã
ç²åºŠãããã±ãŒãžéã®è²¬ä»»ã®åå²ãäžå¿ èŠãªéäžåã«ã€ãªããå Žåãè峿·±ãããšã«ãéã«ãç²åºŠã忣åã«ã€ãªãããšããã¹ãŠãå°ããããããã±ãŒãžã«åå²ãããŸãã Goã§ã¯ãããã¯åŸªç°ã€ã³ããŒãã«ã€ãªããå¯èœæ§ããããŸããããšãã°ãããã¯ã¢ããã¿ãã±ããã远å ã®ããžãã¯ã®åä¿¡ãéå§ãããšãã«çºçããŸãã
é床ã®åçŸæ§..ã Goã§ã¯ããå°ããªã³ããŒã¯å°ããªäŸåé¢ä¿ãããåªããŠããããšãããã¬ãŒãºãäžè¬çã§ããããããããã¯äŸåé¢ä¿ãå°ãªããšããäºå®ã«ã¯ã€ãªãããŸãã-ããã¯ãã ããå€ãã®ã³ããŒã«ãªããŸããç°ãªãGoããã±ãŒãžã®ä»ã®ããã±ãŒãžããã®ã³ãŒãã®ã³ããŒãããç®ã«ããŸãã
ããšãã°ãRobert Martinã¯ã圌ã®èæžãClean Architectureãã«ãéå»ã«Googleãå¯èœãªéãæååãåå©çšããããããå¥ã ã®ã©ã€ãã©ãªã«å²ãåœãŠãå¿ èŠããããšæžããŠããŸããããã«ãããå°ããªãµãŒãã¹ã®2ã3è¡ã倿Žãããä»ã®ãã¹ãŠã®é¢é£ãµãŒãã¹ã«åœ±é¿ãåºãŸãããå瀟ã¯ãŸã ãã®ã¢ãããŒãã®åé¡ãä¿®æ£ããŠããŸãã
ãªãã¡ã¯ã¿ãªã³ã°ããã..ãããã¯æªãã¢ãŒããã¯ãã£ã®ããŒãã¹åºæºã§ãããããããã¥ã¢ã³ã¹ããããŸãããããžã§ã¯ããã©ãã»ã©ã²ã©ãæžããããšããŠããããªããæžãããã©ããã«ããããããæåããæžãçŽããŠã¯ãããŸãããããã¯è¿œå ã®åé¡ãåŒãèµ·ããã ãã§ããå埩ãªãã¡ã¯ã¿ãªã³ã°ãå®è¡ããŸãã
æ¯èŒçæ£ç¢ºã«èšèšããæ¹æ³
ãç§ã®ããããã·ãµãŒãã¹ã¯6ãæéåç¶ãããã®éãã£ãšãã®ã¿ã¹ã¯ãå®è¡ããŸããã§ããã圌ã¯ã©ããã£ãŠãããªã«é·ãçããã®ã§ããïŒ
äŒæ¥ã補åããã¹ããããããç¡å¹ã§ããããšã瀺ãããå Žåããã®è£œåã¯æŸæ£ãŸãã¯ç Žæ£ãããŸããããã¯æ£åžžã§ãã MVPããã¹ãããããããå¹ççã§ããããšã倿ããå Žåãããã¯åç¶ããŸããããããéåžžãMVPã¯æžãçŽãããããçŸç¶ã®ãŸãŸãã§åäœããã³ãŒããšæ©èœã倧ãããªããããŸãããããã£ãŠãMVPçšã«äœæãããããŸã³ã補åãã¯äžè¬çãªæ¹æ³ã§ãããããã·ãµãŒãã¹ã
ã©ã®ããã«æ©èœããŠããªãããç¥ã£ããšããããŒã ã¯ãããæžãçŽãããšã«ããŸããããã®ããžãã¹ã¯ç§ãšååã«å²ãåœãŠããã2é±éå²ãåœãŠãããŸãããããžãã¹ããžãã¯ã¯ã»ãšãã©ãªãããµãŒãã¹ã¯å°èŠæš¡ã§ããããã¯å¥ã®ééãã§ããã
ãµãŒãã¹ã¯å®å šã«æžãçŽããå§ããŸãããã³ãŒãã®äžéšãåãåããæžãçŽããŠãã¹ãç°å¢ã«ã¢ããããŒããããšããã©ãããã©ãŒã ã®äžéšãã¯ã©ãã·ã¥ããŸããããã®ãµãŒãã¹ã«ã¯ã誰ãç¥ããªãææžåãããŠããªãããžãã¹ããžãã¯ãããããããããšã倿ããŸãããååãšç§ã¯å€±æããŸããããããã¯ãµãŒãã¹ããžãã¯ã®ãšã©ãŒã§ãã
å察åŽãããªãã¡ã¯ã¿ãªã³ã°ã«åãçµãããšã«ããŸããã
- åã®ããŒãžã§ã³ã«ããŒã«ããã¯ããŸãã
- ã³ãŒãã¯æžãçŽãããŸããã
- ã³ãŒããããŒãïŒããã±ãŒãžïŒã«åå²ããŸãã
- åããã±ãŒãžã¯åå¥ã®ã€ã³ã¿ãŒãã§ãŒã¹ã«ã©ãããããŠããŸãã
誰ãçè§£ããŠããªãã£ãã®ã§ããµãŒãã¹ãäœãããŠããã®ãçè§£ã§ããŸããã§ããããããã£ãŠããµãŒãã¹ãããŒãã«ããœãŒã€ã³ã°ãããåããŒããäœãæ åœããŠããããææ¡ããããšãå¯äžã®ãªãã·ã§ã³ã§ãã
ãã®åŸãåããã±ãŒãžãåå¥ã«ãªãã¡ã¯ã¿ãªã³ã°ããããšãå¯èœã«ãªããŸããããµãŒãã¹ã®åéšåãåå¥ã«ä¿®æ£ãããããããžã§ã¯ãã®ä»ã®éšåã«å®è£ ãããã§ããŸããåæã«ããµãŒãã¹ã®äœæ¥ã¯ä»æ¥ãŸã§ç¶ããŠããŸãã
ãããªæãã§ããã
æåãããããŸããèšèšããå Žåãåæ§ã®ãµãŒãã¹ãã©ã®ããã«äœæããŸããïŒãŠãŒã¶ãŒãç»é²ããŠæ¿èªããå°ããªãã€ã¯ããµãŒãã¹ã®äŸã玹ä»ããŸãã
å ¥é
ã·ã¹ãã ã®ã³ã¢ãå€éšã¢ãžã¥ãŒã«ãæäœããããšã«ãã£ãŠããžãã¹ããžãã¯ãå®çŸ©ããã³å®è¡ãããšã³ãã£ãã£ãå¿ èŠã§ãã
type Core struct {
userRepo UserRepo
sessionRepo SessionRepo
hashing Hasher
auth Auth
}
次ã«ããªããžããªã¬ã€ã€ãŒã䜿çšã§ããããã«ãã2ã€ã®ã³ã³ãã©ã¯ããå¿ èŠã§ããæåã®å¥çŽã¯ç§ãã¡ã«ã€ã³ã¿ãŒãã§ãŒã¹ãæäŸããŸãããã®å©ããåããŠããŠãŒã¶ãŒã«é¢ããæ å ±ãæ ŒçŽããããŒã¿ããŒã¹å±€ãšéä¿¡ããŸãã
// UserRepo interface for user data repository.
type UserRepo interface {
// CreateUser adds to the new user in repository.
// This method is also required to create a notifying hoard.
// Errors: ErrEmailExist, ErrUsernameExist, unknown.
CreateUser(context.Context, User, TaskNotification) (UserID, error)
// UpdatePassword changes password.
// Resets all codes to reset the password.
// Errors: unknown.
UpdatePassword(context.Context, UserID, []byte) error
// UserByID returning user info by id.
// Errors: ErrNotFound, unknown.
UserByID(context.Context, UserID) (*User, error)
// UserByEmail returning user info by email.
// Errors: ErrNotFound, unknown.
UserByEmail(context.Context, string) (*User, error)
// UserByUsername returning user info by id.
// Errors: ErrNotFound, unknown.
UserByUsername(context.Context, string) (*User, error)
}
2çªç®ã®å¥çŽã¯ããŠãŒã¶ãŒã»ãã·ã§ã³ã«é¢ããæ å ±ãæ ŒçŽããã¬ã€ã€ãŒãšãéä¿¡ãããŸãã
// SessionRepo interface for session data repository.
type SessionRepo interface {
// SaveSession saves the new user Session in a database.
// Errors: unknown.
SaveSession(context.Context, UserID, TokenID, Origin) error
// Session returns user Session.
// Errors: ErrNotFound, unknown.
SessionByTokenID(context.Context, TokenID) (*Session, error)
// UserByAuthToken returning user info by authToken.
// Errors: ErrNotFound, unknown.
UserByTokenID(context.Context, TokenID) (*User, error)
// DeleteSession removes user Session.
// Errors: unknown.
DeleteSession(context.Context, TokenID) error
}
ããã§ããã¹ã¯ãŒããæäœããããã·ã¥ããæ¯èŒããããã®ã€ã³ã¿ãŒãã§ã€ã¹ãå¿ èŠã§ãããŸããèªèšŒããŒã¯ã³ãæäœããããã®ææ°ã®ã€ã³ã¿ãŒãã§ã€ã¹ãããã«ãããèªèšŒããŒã¯ã³ãçæããŠèå¥ã§ããããã«ãªããŸãã
// Hasher module responsible for working with passwords.
type Hasher interface {
// Password returns the hashed version of the password.
// Errors: unknown.
Password(password string) ([]byte, error)
// Compare compares two passwords for matches.
Compare(hashedPassword []byte, password []byte) error
}
// Auth module is responsible for working with authorization tokens.
type Auth interface {
// Token generates an authorization auth with a specified lifetime,
// and can also use the UserID if necessary.
// Errors: unknown.
Token(expired time.Duration) (AuthToken, TokenID, error)
// Parse and validates the auth and checks that it's expired.
// Errors: ErrInvalidToken, ErrExpiredToken, unknown.
Parse(token AuthToken) (TokenID, error)
}
ããžãã¯èªäœãæžãå§ããŸããããäž»ãªè³ªåã¯ãã¢ããªã±ãŒã·ã§ã³ã®ããžãã¹ããžãã¯ã«äœãå¿ èŠããšããããšã§ãã
- ãŠãŒã¶ãŒç»é²ã
- ã¡ãŒã«ãšããã¯ããŒã ã確èªããŠããŸãã
- æ¿èªã
ãã§ãã¯
ç°¡åãªæ¹æ³ããå§ããŸããã-ã¡ãŒã«ãããã¯ããŒã ããã§ãã¯ããŸããUserRepoã«ã¯ç¢ºèªããæ¹æ³ããããŸããããã ãããããã¯è¿œå ããŸããããŠãŒã¶ãŒã«ãã®ããŒã¿ãèŠæ±ããããšã§ããã®ããŒã¿ãŸãã¯ãã®ããŒã¿ãããžãŒã§ãããã©ããã確èªã§ããŸãã
// VerificationEmail for implemented UserApp.
func (a *Application) VerificationEmail(ctx context.Context, email string) error {
_, err := a.userRepo.UserByEmail(ctx, email)
switch {
case errors.Is(err, ErrNotFound):
return nil
case err == nil:
return ErrEmailExist
default:
return err
}
}
// VerificationUsername for implemented UserApp.
func (a *Application) VerificationUsername(ctx context.Context, username string) error {
_, err := a.userRepo.UserByUsername(ctx, username)
switch {
case errors.Is(err, ErrNotFound):
return nil
case err == nil:
return ErrUsernameExist
default:
return err
}
}
ããã«ã¯2ã€ã®ãã¥ã¢ã³ã¹ããããŸãã
ãšã©ãŒãã§ãã¯ã
ErrNotFoundè¡ãã®ã¯ãªãã§ããïŒããžãã¹ããžãã¯ã®å®è£
ã¯ãSQLããã®ä»ã®ããŒã¿ããŒã¹ã«äŸåããŠã¯ãªããªãsql.ErrNoRowsãããããžãã¹ããžãã¯ã«äŸ¿å©ãªãšã©ãŒã«å€æããå¿
èŠããããŸãã
ãŸããAPIã¬ã€ã€ãŒã䜿çšããŠããžãã¹ããžãã¯ã¬ã€ã€ãŒã®ãšã©ãŒãçºçãããŸãããšã©ãŒã³ãŒããªã©ã¯ãAPIã¬ãã«ã§è§£æ±ºããå¿ èŠããããŸããããžãã¹ããžãã¯ã¯ãã¯ã©ã€ã¢ã³ããšã®éä¿¡ãããã³ã«ã«äŸåããŠã¯ãªãããããã«åºã¥ããŠæ±ºå®ãäžãå¿ èŠããããŸãã
ç»é²ãšæ¿èª
// CreateUser for implemented UserApp.
func (a *Application) CreateUser(ctx context.Context, email, username, password string, origin Origin) (*User, AuthToken, error) {
passHash, err := a.password.Password(password)
if err != nil {
return nil, "", err
}
email = strings.ToLower(email)
newUser := User{
Email: email,
Name: username,
PassHash: passHash,
}
_, err = a.userRepo.CreateUser(ctx, newUser)
if err != nil {
return nil, "", err
}
return a.Login(ctx, email, password, origin)
}
// Login for implemented UserApp.
func (a *Application) Login(ctx context.Context, email, password string, origin Origin) (*User, AuthToken, error) {
email = strings.ToLower(email)
user, err := a.userRepo.UserByEmail(ctx, email)
if err != nil {
return nil, "", err
}
if err := a.password.Compare(user.PassHash, []byte(password)); err != nil {
return nil, "", err
}
token, tokenID, err := a.auth.Token(TokenExpire)
if err != nil {
return nil, "", err
}
err = a.sessionRepo.SaveSession(ctx, user.ID, tokenID, origin)
if err != nil {
return nil, "", err
}
return user, token, nil
}
ããã¯ãèªã¿ããããä¿å®ããããã·ã³ãã«ã§å¿ é ã®ã³ãŒãã§ããèšèšæã«ãã®ã³ãŒããããã«æžãå§ããããšãã§ããŸãããŠãŒã¶ãŒã远å ããããŒã¿ããŒã¹ãã¯ã©ã€ã¢ã³ããšã®éä¿¡ã«éžæãããããã³ã«ããŸãã¯ãã¹ã¯ãŒãã®ããã·ã¥æ¹æ³ã¯é¢ä¿ãããŸãããããžãã¹ããžãã¯ã¯ããããã¹ãŠã®ã¬ã€ã€ãŒã«é¢å¿ãããããã§ã¯ãªããã¢ããªã±ãŒã·ã§ã³é åã®ã¿ã¹ã¯ãå®è¡ããããšã ããéèŠã§ãã
ã·ã³ãã«ãªããã·ã¥ã¬ã€ã€ãŒ
ã©ãããæå³ã§ããïŒãã¹ãŠã®å€éšã®éã¬ã€ã€ãŒã¯ãã¢ããªã±ãŒã·ã§ã³é åã«é¢é£ããã¿ã¹ã¯ã«ã€ããŠæ±ºå®ãäžãã¹ãã§ã¯ãããŸããã圌ãã¯ç§ãã¡ã®ããžãã¹ããžãã¯ãå¿ èŠãšããç¹å®ã®åçŽãªã¿ã¹ã¯ãå®è¡ããŸããããšãã°ããã¹ã¯ãŒããããã·ã¥ããããã®ã¬ã€ã€ãŒãèããŠã¿ãŸãããã
// Package hasher contains methods for hashing and comparing passwords.
package hasher
import (
"errors"
"github.com/zergslaw/boilerplate/internal/app"
"golang.org/x/crypto/bcrypt"
)
type (
// Hasher is an implements app.Hasher.
// Responsible for working passwords, hashing and compare.
Hasher struct {
cost int
}
)
// New creates and returns new app.Hasher.
func New(cost int) app.Hasher {
return &Hasher{cost: cost}
}
// Hashing need for implements app.Hasher.
func (h *Hasher) Password(password string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), h.cost)
}
// Compare need for implements app.Hasher.
func (h *Hasher) Compare(hashedPassword []byte, password []byte) error {
err := bcrypt.CompareHashAndPassword(hashedPassword, password)
switch {
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
return app.ErrNotValidPassword
case err != nil:
return err
}
return nil
}
ããã¯ããã¹ã¯ãŒãã®ããã·ã¥ããã³æ¯èŒã¿ã¹ã¯ãå®è¡ããããã®ããã€ãã®åçŽãªã¬ã€ã€ãŒã§ããããããã¹ãŠã§ãã圌ã¯ç©ããŠããŠã·ã³ãã«ã§ãä»ã«äœãç¥ããŸããããããŠããããã¹ãã§ã¯ãããŸããã
ã¬ã
ã¹ãã¬ãŒãžã€ã³ã¿ã©ã¯ã·ã§ã³ã¬ã€ã€ãŒã«ã€ããŠèããŠã¿ãŸãããã
å®è£ ã宣èšããå®è£ ããå¿ èŠã®ããã€ã³ã¿ãŒãã§ã€ã¹ã瀺ããŸãããã
var _ app.SessionRepo = &Repo{}
var _ app.UserRepo = &Repo{}
// Repo is an implements app.UserRepo.
// Responsible for working with database.
type Repo struct {
db *sqlx.DB
}
// New creates and returns new app.UserRepo.
func New(repo *sqlx.DB) *Repo {
return &Repo{db: repo}
}
ã³ãŒãã®èªè ã«ãã¬ã€ã€ãŒã«ãã£ãŠå®è£ ãããŠããã³ã³ãã©ã¯ããçè§£ããããªããžããªã«èšå®ãããŠããã¿ã¹ã¯ãèæ ®ã«å ¥ããããšãã§ããŸãã
å®è£ ã«åãââæãããŸããããèšäºãåŒã䌞ã°ããªãããã«ãã¡ãœããã®äžéšã®ã¿ã瀺ããŸãã
// CreateUser need for implements app.UserRepo.
func (repo *Repo) CreateUser(ctx context.Context, newUser app.User, task app.TaskNotification) (userID app.UserID, err error) {
const query = `INSERT INTO users (username, email, pass_hash) VALUES ($1, $2, $3) RETURNING id`
hash := pgtype.Bytea{
Bytes: newUser.PassHash,
Status: pgtype.Present,
}
err = repo.db.QueryRowxContext(ctx, query, newUser.Name, newUser.Email, hash).Scan(&userID)
if err != nil {
return 0, fmt.Errorf("create user: %w", err)
}
return userID, nil
}
// UserByUsername need for implements app.UserRepo.
func (repo *Repo) UserByUsername(ctx context.Context, username string) (user *app.User, err error) {
const query = `SELECT * FROM users WHERE username = $1`
u := &userDBFormat{}
err = repo.db.GetContext(ctx, u, query, username)
if err != nil {
return nil, err
}
return u.toAppFormat(), nil
}
Repoã¬ã€ã€ãŒã«ã¯ãã·ã³ãã«ã§åºæ¬çãªæ¹æ³ããããŸãã圌ãã¯ãä¿åãéä¿¡ãæŽæ°ãåé€ãæ€çŽ¢ã以å€ã®æ¹æ³ãç¥ããŸãããã¬ã€ã€ãŒã®ã¿ã¹ã¯ã¯ããããžã§ã¯ããå¿ èŠãšããããŒã¿ããŒã¹ãžã®ããŒã¿ã®äŸ¿å©ãªãããã€ããŒã«ãªãããšã ãã§ãã
API
ã¯ã©ã€ã¢ã³ããšå¯Ÿè©±ããããã®APIã¬ã€ã€ãŒããŸã ãããŸãã
ã¯ã©ã€ã¢ã³ãããããžãã¹ããžãã¯ã«ããŒã¿ã転éããçµæãè¿ãããã¹ãŠã®HTTPããŒãºãå®å šã«æºããå¿ èŠããããŸã-ã¢ããªã±ãŒã·ã§ã³ãšã©ãŒã倿ããŸãã
func (api *api) handler(w http.ResponseWriter, r *http.Request) {
params := &arg{}
err := json.NewDecoder(r.Body).Decode(params)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
origin := orifinFromReq(r)
res, err := api.app.CreateUser(
r.Context(),
params.Email,
params.Username,
params.Password,
request,
)
switch {
case errors.Is(err, app.ErrNotFound):
http.Error(w, app.ErrNotFound.Error(), http.StatusNotFound)
case errors.Is(err, app.ErrChtoto):
http.Error(w, app.ErrChtoto.Error(), http.StatusTeapot)
case err == nil:
json.NewEncoder(w).Encode(res)
default:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
ããã§ã圌ã®ã¿ã¹ã¯ã¯çµäºããŸãã圌ã¯ããŒã¿ãæã£ãŠããŠãçµæãååŸãããããHTTPã«äŸ¿å©ãªåœ¢åŒã«å€æããŸããã
ã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ãæ¬åœã«å¿ èŠãªã®ã¯äœã§ããïŒ
ããã¯äœã®ããã§ããïŒãªãç¹å®ã®ã¢ãŒããã¯ãã£ãœãªã¥ãŒã·ã§ã³ãå®è£ ããã®ã§ããïŒã³ãŒãã®ãã¯ãªãŒã³ããã®ããã§ã¯ãªãããã¹ãå®¹ææ§ã®ããã§ããç¬èªã®ã³ãŒãã䟿å©ãç°¡åãç°¡åã«ãã¹ãããæ©èœãå¿ èŠã§ãã
ããšãã°ããã®ãããªã³ãŒãã¯æªãã§ãïŒ
func (api *api) handler(w http.ResponseWriter, r *http.Request) {
params := &arg{}
err := json.NewDecoder(r.Body).Decode(params)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
rows, err := api.db.QueryContext(r.Context(), "sql query", params.Param)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var arrayRes []val
for rows.Next() {
value := val{}
err := rows.Scan(&value)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
arrayRes = append(arrayRes, value)
}
//
err = json.NewEncoder(w).Encode(arrayRes)
w.WriteHeader(http.StatusOK)
}
泚ïŒãã®ã³ãŒããæªãããšãææããã®ãå¿ããŸãããæŽæ°åã«èªãã å Žåãããã¯èª€è§£ãæãå¯èœæ§ããããŸããç³ãèš³ãããŸããã
倧ããªåé¡ãªãã«ã³ãŒãããã¹ãã§ããããšã¯ãã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ã®äž»ãªå©ç¹ã§ãã
ããŒã¿ããŒã¹ããµãŒããŒããããã³ã«ããæœè±¡åããããšã§ããã¹ãŠã®ããžãã¹ããžãã¯ããã¹ãã§ããŸããã¢ããªã±ãŒã·ã§ã³ã®é©çšãããã¿ã¹ã¯ãå®è¡ããããšã ããéèŠã§ããããã§ãç¹å®ã®åçŽãªã«ãŒã«ã«åŸã£ãŠãã³ãŒããç°¡åã«æ¡åŒµããã³å€æŽããããšãã§ããŸãã
ã©ã®è£œåã«ãããžãã¹ããžãã¯ããããŸããåªããã¢ãŒããã¯ãã£ã¯ãããšãã°ãããžãã¹ããžãã¯ã1ã€ã®ããã±ãŒãžã«ããã¯ããã®ã«åœ¹ç«ã¡ãŸãããã®ã¿ã¹ã¯ã¯ãå€éšã¢ãžã¥ãŒã«ãæäœããŠã¢ããªã±ãŒã·ã§ã³ã¿ã¹ã¯ãå®è¡ããããšã§ãã
ããããã¯ãªãŒã³ãªã¢ãŒããã¯ãã£ãåžžã«è¯ããšã¯éããŸãããæã«ã¯ããã¯æªã«å€ãããäžå¿ èŠãªè€éããããããå¯èœæ§ããããŸããããã«å®ç§ã«æžã蟌ãããšãããšã貎éãªæéãç¡é§ã«ããŠãããžã§ã¯ãã倱æãããŠããŸããŸããããªãã¯å®ç§ã«æžãå¿ èŠã¯ãããŸãã-ããªãã®ããžãã¹ç®æšã«åºã¥ããŠããŸãæžããŠãã ããã
, Golang Live 2020 14 17 . â 14 , â , .