Last active
August 26, 2024 09:14
-
-
Save ne-sachirou/be88c43cead2e689ea248ab82c426bfc to your computer and use it in GitHub Desktop.
函數型ドメインモデリング 第12章 永續化 Go
This file contains hidden or 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
package main | |
import ( | |
"context" | |
"errors" | |
"github.com/ne-sachirou/dmmf-go/query" | |
) | |
type EmailAddress string | |
func NewEmailAddress(emailAddress string) (EmailAddress, error) { | |
return EmailAddress(emailAddress), nil | |
} | |
type PhoneNumber string | |
func NewPhoneNumber(phoneNumber string) (PhoneNumber, error) { | |
return PhoneNumber(phoneNumber), nil | |
} | |
type ContactInfo interface{} | |
type Email struct { | |
ContactInfo | |
EmailAddress EmailAddress | |
} | |
type Phone struct { | |
ContactInfo | |
PhoneNumber PhoneNumber | |
} | |
type Contact struct { | |
ContactId int64 | |
Info ContactInfo | |
} | |
type ContactRecord struct { | |
ContactId int64 | |
EmailAddress *string | |
PhoneNumber *string | |
} | |
var InvalidContactRecord = errors.New("invalid contact record") | |
func ContactRecordToDomain(dbRecord ContactRecord) (Contact, error) { | |
var info ContactInfo | |
if dbRecord.EmailAddress != nil { | |
emailAddress, err := NewEmailAddress(*dbRecord.EmailAddress) | |
if err != nil { | |
return Contact{}, errors.Join(InvalidContactRecord, err) | |
} | |
info = Email{EmailAddress: emailAddress} | |
} else if dbRecord.PhoneNumber != nil { | |
phoneNumber, err := NewPhoneNumber(*dbRecord.PhoneNumber) | |
if err != nil { | |
return Contact{}, errors.Join(InvalidContactRecord, err) | |
} | |
info = Phone{PhoneNumber: phoneNumber} | |
} else { | |
return Contact{}, InvalidContactRecord | |
} | |
return Contact{ | |
ContactId: dbRecord.ContactId, | |
Info: info, | |
}, nil | |
} | |
var ContactNotFound = errors.New("contact not found") | |
var MultipleContactsFound = errors.New("multiple contacts found") | |
// Gen/GORM を想定 | |
func ReadOneContact(ctx context.Context, c query.Contact, contactId int64) (Contact, error) { | |
var records []ContactRecord | |
records, err := c.WithContext(ctx).Where(c.ContactId.EQ(contactId)).Find() | |
if err != nil { | |
return Contact{}, err | |
} | |
if len(records) == 0 { | |
return Contact{}, ContactNotFound | |
} | |
if len(records) > 1 { | |
return Contact{}, MultipleContactsFound | |
} | |
return ContactRecordToDomain(records[0]) | |
} | |
// Gen/GORM を想定 | |
func WriteContect(ctx context.Context, c query.Contact, contact Contact) error { | |
record := ContactRecord{ | |
ContactId: contact.ContactId, | |
} | |
switch info := contact.Info.(type) { | |
case Email: | |
record.EmailAddress = (*string)(&info.EmailAddress) | |
case Phone: | |
record.PhoneNumber = (*string)(&info.PhoneNumber) | |
default: | |
return errors.New("invalid contact info") | |
} | |
if err := c.WithContext(ctx).Create(&record); err != nil { | |
return err | |
} | |
if err := c.WithContext(ctx).Where(c.ContactId.EQ(contact.ContactId)).Update(record.EmailAddress, record.PhoneNumber); err != nil { | |
return err | |
} | |
return nil | |
} |
This file contains hidden or 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
package main | |
import ( | |
"context" | |
"errors" | |
"time" | |
"github.com/ne-sachirou/dmmf-go/query" | |
) | |
type Customer struct { | |
CustomerID int64 | |
Name string | |
Birthdate *time.Time | |
} | |
type CustomerRecord struct { | |
CustomerID int64 | |
Name string | |
Birthdate *string | |
} | |
var InvalidCustomerRecord = errors.New("invalid customer record") | |
func CustomerRecordToDomain(dbRecord CustomerRecord) (Customer, error) { | |
var birthdate *time.Time | |
if dbRecord.Birthdate == nil { | |
birthdate = nil | |
} else { | |
b, err := time.Parse("2006-01-02", *dbRecord.Birthdate) | |
if err != nil { | |
return Customer{}, errors.Join(InvalidCustomerRecord, err) | |
} | |
birthdate = &b | |
} | |
return Customer{ | |
CustomerID: dbRecord.CustomerID, | |
Name: dbRecord.Name, | |
Birthdate: birthdate, | |
}, nil | |
} | |
var CustomerNotFound = errors.New("customer not found") | |
var MultipleCustomersFound = errors.New("multiple customers found") | |
// Gen/GORM を想定 | |
func (c query.Customer) ReadOneCustomer(ctx context.Context, customerId int64) (Customer, error) { | |
var records []CustomerRecord | |
records, err := c.WithContext(ctx).Where(c.CustomerID.EQ(customerId)).Find() | |
if err != nil { | |
return Customer{}, err | |
} | |
if len(records) == 0 { | |
return Customer{}, CustomerNotFound | |
} | |
if len(records) > 1 { | |
return Customer{}, MultipleCustomersFound | |
} | |
customer, err := CustomerRecordToDomain(records[0]) | |
if err != nil { | |
return Customer{}, err | |
} | |
return customer, nil | |
} |
This file contains hidden or 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
module github.com/ne-sachirou/dmmf-go | |
go 1.23.0 | |
require gorm.io/gorm v1.25.11 | |
require ( | |
github.com/jinzhu/inflection v1.0.0 // indirect | |
github.com/jinzhu/now v1.1.5 // indirect | |
golang.org/x/text v0.14.0 // indirect | |
) |
This file contains hidden or 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
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | |
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | |
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= | |
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= | |
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | |
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | |
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= | |
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= |
This file contains hidden or 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
package main | |
func main() { | |
} |
This file contains hidden or 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
package main | |
import ( | |
"context" | |
"gorm.io/gorm" | |
"github.com/ne-sachirou/dmmf-go/query" | |
) | |
type Invoice interface{} | |
type UnpaidInvoice struct { | |
Invoice | |
InvoiceId int64 | |
} | |
type PaidInvoice struct { | |
Invoice | |
InvoiceId int64 | |
} | |
type InvoicePaymentResult interface{} | |
type FullyPaid struct { | |
InvoicePaymentResult | |
} | |
type PartiallyPaid struct { | |
InvoicePaymentResult | |
Invoice Invoice | |
} | |
func isFullyPaid(paymentResult InvoicePaymentResult) bool { | |
_, ok := paymentResult.(FullyPaid) | |
return ok | |
} | |
type PayInvoiceCommand struct { | |
InvoiceId int64 | |
Payment int64 | |
} | |
func postInvoicePaidEvent(invoiceId int64) {} | |
func applyPayment(unpaidInvoice Invoice, payment int64) InvoicePaymentResult { | |
// なあにこれ? | |
updatedInvoice := applyPayment(unpaidInvoice, payment) | |
if isFullyPaid(updatedInvoice) { | |
return FullyPaid{} | |
} else { | |
return PartiallyPaid{Invoice: updatedInvoice} | |
} | |
} | |
type Service interface { | |
LoadUnpaiedInvoiceFromDatabase(ctx context.Context, q query.Invoice, invoiceId int64) (UnpaidInvoice, error) | |
MarkAsFullyPaidInDb(ctx context.Context, q query.Invoice, invoiceId int64) error | |
MarkPaymentCompleted(ctx context.Context, q query.Payment, paymentId int64) error | |
UpdateInvoiceInDb(ctx context.Context, q query.Invoice, invoice Invoice) error | |
} | |
var db *gorm.DB | |
// Gen/GORM を想定 | |
func PayInvoice(ctx context.Context, payInvoiceCommand PayInvoiceCommand, service Service) error { | |
q := query.Use(db) | |
return q.Transaction(func(tx *query.Query) error { | |
invoiceId := payInvoiceCommand.InvoiceId | |
unpayedInvoice, err := service.LoadUnpaiedInvoiceFromDatabase(ctx, tx.Invoice, invoiceId) | |
if err != nil { | |
return err | |
} | |
payment := payInvoiceCommand.Payment | |
paymentResult := applyPayment(unpayedInvoice, payment) | |
switch r := paymentResult.(type) { | |
case FullyPaid: | |
if err := service.MarkAsFullyPaidInDb(ctx, tx.Invoice, invoiceId); err != nil { | |
return err | |
} | |
if err := service.MarkPaymentCompleted(ctx, tx.Payment, invoiceId); err != nil { | |
return err | |
} | |
postInvoicePaidEvent(invoiceId) | |
case PartiallyPaid: | |
if err := service.UpdateInvoiceInDb(ctx, tx.Invoice, r.Invoice); err != nil { | |
return err | |
} | |
} | |
return nil | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment