SlideShare a Scribd company logo
1 of 61
Download to read offline
Microservices
dengan Go-kit
Bandung, Februari 2020
Irfan Aris Nur Hakim
Software Engineer at HOOQ
@irfannurhakim
Hari ini
• Sedikit tentang Microservices

• Sedikit tentang Bahasa Pemrograman Go

• Go-kit
! 5 Negara

☀ 100juta++ requests per hari

📽 20juta++ streaming minutes per hari

⚡ 50Gbps++ bandwidth saat puncak

🌻 50++ services
Microservices
Microservices
• Ditulis dalam bahasa pemgorgraman apa pun, dan
framework apa pun.

• Independen; kode, versi, deployment, scaled.

• Berinteraksi dengan microservices lainnya melalui interfaces
atau protokol lain yang terdefinisi - kontrak. 

• Mempunyai alamat/nama yang unik (URLs) untuk digunakan
dalam mengaksesnya.

• Tetap konsisten dan tersedia dimana terjadinya suatu
kegagalan.
–Peter Bourgon
Microservices solve organizational problems

~

Microservices cause technical problems
Masalah
• Dibutuhkan domain bisnis yang telah terdifinisi dengan baik
untuk menghasilkan API yang stabil.

• Tidak ada lagi shared-database.

• Testing yang menjadi sangat sulit.

• Pipeline - Memerlukan kultur dev/ops yang berjalan.

• Monitoring dan instrumentasi — tail -f? Nagios & New Relic?
Ha! Distributed tracing?

• Keamanan.
Go
Go
• Sangan mudah dipelajari, dikuasai, dan di-maintain.

• Kompilasi cepat.

• Native.

• Statically Typed.

• Koleksi Standard Library yang lengkap.

• Garbage collected.
• dll
Go-Kit
Kenapa dibuat?
Komparasi
• Micro (go)

• Finagle (scala)

• Spring Boot (java)

• Tokio (rust)
Tinjauan Arsitektur
• Transport 

• Endpoint

• Service
Terdiri dari tiga komponen utama:
Tinjauan Arsitektur
Source code dependencies can only point inwards. In other
words, the source code of each circle can only access code
in an inner circle but never any code in an outer circle.
Request & Response
Transport ———— Endpoint ———— Service
Request
Response
Simple Svc
//	SimpleService	describes	a	service	that	adds	things	together.	
type	SimpleService	interface	{	
		Sum(a,	b	int)	(int,	error)	
		Concat(a,	b	string)	(string,	error)	
}	
type	simpleService	struct{}	
func	(s	*simpleService)	Sum(a,	b	int)	(int,	error)	{	
		if	a	==	0	&&	b	==	0	{	
				return	0,	ErrTwoZeroes	
		}	
		if	(b	>	0	&&	a	>	(intMax-b))	||	(b	<	0	&&	a	<	(intMin-b))	{	
				return	0,	ErrIntOverflow	
		}	
		return	a	+	b,	nil	
}	
func	(s	*simpleService)	Concat(a,	b	string)	(string,	error)	{	
		if	len(a)+len(b)	>	maxLen	{	
				return	"",	ErrMaxSizeExceeded	
		}	
		return	a	+	b,	nil	
}
Simple Svc
//	ServeHTTP	turns	simpleService	into	an	HTTP	handler	
func	(s	*simpleService)	ServeHTTP(w	http.ResponseWriter,	r	*http.Request)	{	
		switch	r.URL.Path[1:]	{	
		case	“sum":	
				keys	:=	r.URL.Query()	
				a,	_	:=	strconv.Atoi(keys.Get("a"))	
				b,	_	:=	strconv.Atoi(keys.Get("b"))	
				c,	err	:=	s.Sum(a,	b)	
				if	err	!=	nil	{	
						http.Error(w,	err.Error(),	http.StatusInternalServerError)	
						return	
				}	
				fmt.Fprintf(w,	strconv.Itoa(c))	
		case	"concat":	
				keys	:=	r.URL.Query()	
				a	:=	keys.Get("a")	
				b	:=	keys.Get("b")	
				c,	err	:=	s.Concat(a,	b)	
				if	err	!=	nil	{	
						http.Error(w,	err.Error(),	http.StatusInternalServerError)	
						return	
				}	
				fmt.Fprintf(w,	c)	
		}	
}
Simple Svc
func	main()	{	
		ss	:=	&simpleService{}	
		http.ListenAndServe(":9090",	ss)	
}
Core
Logic
Transport
Core
Logic
Logging
Transport
Logging
func	(s	*simpleService)	ServeHTTP(w	http.ResponseWriter,	r	*http.Request)	{	
		switch	r.URL.Path[1:]	{	
		case	"sum":	
				keys	:=	r.URL.Query()	
				a,	_	:=	strconv.Atoi(keys.Get("a"))	
				b,	_	:=	strconv.Atoi(keys.Get("b"))	
				c,	err	:=	s.Sum(a,	b)	
				if	err	!=	nil	{	
						code	:=	http.StatusInternalServerError	
						http.Error(w,	err.Error(),	code)	
						log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	code)	
						return	
				}	
				fmt.Fprintf(w,	strconv.Itoa(c))	
				log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	200)
Logging
case	"concat":	
				keys	:=	r.URL.Query()	
				a	:=	keys.Get("a")	
				b	:=	keys.Get("b")	
				c,	err	:=	s.Concat(a,	b)	
				if	err	!=	nil	{	
						code	:=	http.StatusInternalServerError	
						http.Error(w,	err.Error(),	code)	
						log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	code)	
						return	
				}	
				fmt.Fprintf(w,	c)	
				log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	200)	
		}	
}
Logging
func	main()	{	
		ss	:=	&simpleService{}	
		log.Printf(“listening:9090")	
		log.Fatal(http.ListenAndServe(":9090",	ss))	
}
Core
Logic
Logging
Metrics
Transport
Instrumentasi
var	dur	=	prometheus.NewSummaryVec(prometheus.SummaryOpts{	
				Namespace:	"simple_space",	
				Subsystem:	"simple_service",	
				Name:						"http_request_duration_seconds",	
				Help:						"Time	spent	serving	HTTP	requests.",	
},	[]string{"method",	"status_code"})
Instrumentasi
func	(s	*simpleService)	ServeHTTP(w	http.ResponseWriter,	r	*http.Request)	{	
		begin	:=	time.Now()	
		switch	r.URL.Path[1:]	{	
		case	"sum":	
			keys	:=	r.URL.Query()	
				a,	_	:=	strconv.Atoi(keys.Get("a"))	
				b,	_	:=	strconv.Atoi(keys.Get("b"))	
				c,	err	:=	s.Sum(a,	b)	
				if	err	!=	nil	{	
						code	:=	http.StatusInternalServerError	
						http.Error(w,	err.Error(),	code)	
						log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	code)	
						dur.WithLabelValues(r.URL.Path[1:],	
fmt.Sprint(code)).Observe(time.Since(begin).Seconds())	
						return	
				}	
				fmt.Fprintf(w,	strconv.Itoa(c))	
				log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	200)
Instrumentasi
case	"concat":	
			keys	:=	r.URL.Query()	
				a	:=	keys.Get("a")	
				b	:=	keys.Get("b")	
				c,	err	:=	s.Concat(a,	b)	
				if	err	!=	nil	{	
						code	:=	http.StatusInternalServerError	
						http.Error(w,	err.Error(),	code)	
						log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	code)	
						dur.WithLabelValues(r.URL.Path[1:],	
fmt.Sprint(code)).Observe(time.Since(begin).Seconds())	
						return	
				}	
				fmt.Fprintf(w,	c)	
				log.Printf("%s:	%s	%s:	%d",	r.RemoteAddr,	r.Method,	r.URL.Path[1:],	200)	
				dur.WithLabelValues(r.URL.Path[1:],	
fmt.Sprint(200)).Observe(time.Since(begin).Seconds())	
		}	
}
Core
Logic
Logging
Metrics
Circuit
Breaker
Rate
Limiter
Transport
Audit
Logging
Tracing:(
Business logic masih bersih, tapi untuk handler tadi jadi “Mixed concern”
Tinjauan Arsitektur
Transport Layer
• Layer paling luar, untuk komunikasi/akses.

• Mendukung beberapa protokol komunikasi, diantaranya
HTTP, gRPC, AMQP, Thirft, NATS, dll.

• Satu microservices bisa dipasang beberapa jenis
transport.
• Decoder/Encoder
• Kirim data ke layer selanjutnya —> Endpoint Layer
HTTP
//	NewHTTPHandler	returns	an	HTTP	handler	that	makes	a	set	of	endpoints	
//	available	on	predefined	paths.	
func	NewHTTPHandler(endpoints	addendpoint.Set,	otTracer	stdopentracing.Tracer,	zipkinTracer	*stdzipkin.Tracer,	logger	
log.Logger)	http.Handler	{	
	 options	:=	[]httptransport.ServerOption{	
	 	 httptransport.ServerErrorEncoder(errorEncoder),	
	 	 httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)),	
	 }	
	 m	:=	http.NewServeMux()	
	 m.Handle("/sum",	httptransport.NewServer(	
	 	 endpoints.SumEndpoint,	
	 	 decodeHTTPSumRequest,	
	 	 encodeHTTPGenericResponse,	
	 	 append(options,	httptransport.ServerBefore(opentracing.HTTPToContext(otTracer,	"Sum",	logger)))...,	
	 ))	
	 m.Handle("/concat",	httptransport.NewServer(	
	 	 endpoints.ConcatEndpoint,	
	 	 decodeHTTPConcatRequest,	
	 	 encodeHTTPGenericResponse,	
	 	 append(options,	httptransport.ServerBefore(opentracing.HTTPToContext(otTracer,	"Concat",	logger)))...,	
	 ))	
	 return	m	
}
HTTP
//	decodeHTTPSumRequest	is	a	transport/http.DecodeRequestFunc	that	decodes	a	
//	JSON-encoded	sum	request	from	the	HTTP	request	body.	Primarily	useful	in	a	
//	server.	
func	decodeHTTPSumRequest(_	context.Context,	r	*http.Request)	(interface{},	error)	{	
		var	req	addendpoint.SumRequest	
		err	:=	json.NewDecoder(r.Body).Decode(&req)	
		return	req,	err	
}	
//	encodeHTTPGenericResponse	is	a	transport/http.EncodeResponseFunc	that	encodes	
//	the	response	as	JSON	to	the	response	writer.	Primarily	useful	in	a	server.	
func	encodeHTTPGenericResponse(ctx	context.Context,	w	http.ResponseWriter,	response	interface{})	error	{	
		if	f,	ok	:=	response.(endpoint.Failer);	ok	&&	f.Failed()	!=	nil	{	
				errorEncoder(ctx,	f.Failed(),	w)	
				return	nil	
		}	
		w.Header().Set("Content-Type",	"application/json;	charset=utf-8")	
		return	json.NewEncoder(w).Encode(response)	
}
gRPC
//	NewGRPCServer	makes	a	set	of	endpoints	available	as	a	gRPC	AddServer.	
func	NewGRPCServer(endpoints	addendpoint.Set,	otTracer	stdopentracing.Tracer,	zipkinTracer	*stdzipkin.Tracer,	logger	
log.Logger)	pb.AddServer	{	
	 options	:=	[]grpctransport.ServerOption{	
	 	 grpctransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)),	
	 }	
	 return	&grpcServer{	
	 	 sum:	grpctransport.NewServer(	
	 	 	 endpoints.SumEndpoint,	
	 	 	 decodeGRPCSumRequest,	
	 	 	 encodeGRPCSumResponse,	
	 	 	 append(options,	grpctransport.ServerBefore(opentracing.GRPCToContext(otTracer,	"Sum",	logger)))...,	
	 	 ),	
	 	 concat:	grpctransport.NewServer(	
	 	 	 endpoints.ConcatEndpoint,	
	 	 	 decodeGRPCConcatRequest,	
	 	 	 encodeGRPCConcatResponse,	
	 	 	 append(options,	grpctransport.ServerBefore(opentracing.GRPCToContext(otTracer,	"Concat",	logger)))...,	
	 	 ),	
	 }	
}
Endpoint Layer
• Seperti handler pada sebuah controller.
• Terima interface{} sbg Request, kembalikan interface{}
Response.

• Setiap endpoint ini mengekspos service terhadap dunia
luar melalui Transport.
//	MakeSumEndpoint	constructs	a	Sum	endpoint	wrapping	the	service.	
func	MakeSumEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(SumRequest)	
				v,	err	:=	s.Sum(ctx,	req.A,	req.B)	
				return	SumResponse{V:	v,	Err:	err},	nil	
		}	
}	
//	MakeConcatEndpoint	constructs	a	Concat	endpoint	wrapping	the	service.	
func	MakeConcatEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(ConcatRequest)	
				v,	err	:=	s.Concat(ctx,	req.A,	req.B)	
				return	ConcatResponse{V:	v,	Err:	err},	nil	
		}	
}
//	MakeSumEndpoint	constructs	a	Sum	endpoint	wrapping	the	service.	
func	MakeSumEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(SumRequest)	
				v,	err	:=	s.Sum(ctx,	req.A,	req.B)	
				return	SumResponse{V:	v,	Err:	err},	nil	
		}	
}	
//	MakeConcatEndpoint	constructs	a	Concat	endpoint	wrapping	the	service.	
func	MakeConcatEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(ConcatRequest)	
				v,	err	:=	s.Concat(ctx,	req.A,	req.B)	
				return	ConcatResponse{V:	v,	Err:	err},	nil	
		}	
}
//	MakeSumEndpoint	constructs	a	Sum	endpoint	wrapping	the	service.	
func	MakeSumEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(SumRequest)	
				v,	err	:=	s.Sum(ctx,	req.A,	req.B)	
				return	SumResponse{V:	v,	Err:	err},	nil	
		}	
}	
//	MakeConcatEndpoint	constructs	a	Concat	endpoint	wrapping	the	service.	
func	MakeConcatEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(ConcatRequest)	
				v,	err	:=	s.Concat(ctx,	req.A,	req.B)	
				return	ConcatResponse{V:	v,	Err:	err},	nil	
		}	
}
//	MakeSumEndpoint	constructs	a	Sum	endpoint	wrapping	the	service.	
func	MakeSumEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(SumRequest)	
				v,	err	:=	s.Sum(ctx,	req.A,	req.B)	
				return	SumResponse{V:	v,	Err:	err},	nil	
		}	
}	
//	MakeConcatEndpoint	constructs	a	Concat	endpoint	wrapping	the	service.	
func	MakeConcatEndpoint(s	addservice.Service)	endpoint.Endpoint	{	
		return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
				req	:=	request.(ConcatRequest)	
				v,	err	:=	s.Concat(ctx,	req.A,	req.B)	
				return	ConcatResponse{V:	v,	Err:	err},	nil	
		}	
}
Service Layer
• Bagian dimana business logic diimplementasi.

• Biasanya dimodelkan dengan interface, dimana
implementasi konkretnya berisi business logic.

• Tidak ada knowledge terhadap layer sebelumnya,
misalnya HTTP Headers (Transport Layer).
//	Service	describes	a	service	that	adds	things	together.	
type	Service	interface	{	
	 Sum(ctx	context.Context,	a,	b	int)	(int,	error)	
	 Concat(ctx	context.Context,	a,	b	string)	(string,	error)	
}	
type	basicService	struct{}	
func	(s	basicService)	Sum(_	context.Context,	a,	b	int)	(int,	error)	{	
	 if	a	==	0	&&	b	==	0	{	
	 	 return	0,	ErrTwoZeroes	
	 }	
	 if	(b	>	0	&&	a	>	(intMax-b))	||	(b	<	0	&&	a	<	(intMin-b))	{	
	 	 return	0,	ErrIntOverflow	
	 }	
	 return	a	+	b,	nil	
}	
//	Concat	implements	Service.	
func	(s	basicService)	Concat(_	context.Context,	a,	b	string)	(string,	error)	{	
	 if	len(a)+len(b)	>	maxLen	{	
	 	 return	"",	ErrMaxSizeExceeded	
	 }	
	 return	a	+	b,	nil	
}
//	Service	describes	a	service	that	adds	things	together.	
type	Service	interface	{	
	 Sum(ctx	context.Context,	a,	b	int)	(int,	error)	
	 Concat(ctx	context.Context,	a,	b	string)	(string,	error)	
}	
type	basicService	struct{}	
func	(s	basicService)	Sum(_	context.Context,	a,	b	int)	(int,	error)	{	
	 if	a	==	0	&&	b	==	0	{	
	 	 return	0,	ErrTwoZeroes	
	 }	
	 if	(b	>	0	&&	a	>	(intMax-b))	||	(b	<	0	&&	a	<	(intMin-b))	{	
	 	 return	0,	ErrIntOverflow	
	 }	
	 return	a	+	b,	nil	
}	
//	Concat	implements	Service.	
func	(s	basicService)	Concat(_	context.Context,	a,	b	string)	(string,	error)	{	
	 if	len(a)+len(b)	>	maxLen	{	
	 	 return	"",	ErrMaxSizeExceeded	
	 }	
	 return	a	+	b,	nil	
}
//	Service	describes	a	service	that	adds	things	together.	
type	Service	interface	{	
	 Sum(ctx	context.Context,	a,	b	int)	(int,	error)	
	 Concat(ctx	context.Context,	a,	b	string)	(string,	error)	
}	
type	basicService	struct{}	
func	(s	basicService)	Sum(_	context.Context,	a,	b	int)	(int,	error)	{	
	 if	a	==	0	&&	b	==	0	{	
	 	 return	0,	ErrTwoZeroes	
	 }	
	 if	(b	>	0	&&	a	>	(intMax-b))	||	(b	<	0	&&	a	<	(intMin-b))	{	
	 	 return	0,	ErrIntOverflow	
	 }	
	 return	a	+	b,	nil	
}	
//	Concat	implements	Service.	
func	(s	basicService)	Concat(_	context.Context,	a,	b	string)	(string,	error)	{	
	 if	len(a)+len(b)	>	maxLen	{	
	 	 return	"",	ErrMaxSizeExceeded	
	 }	
	 return	a	+	b,	nil	
}
//	Service	describes	a	service	that	adds	things	together.	
type	Service	interface	{	
	 Sum(ctx	context.Context,	a,	b	int)	(int,	error)	
	 Concat(ctx	context.Context,	a,	b	string)	(string,	error)	
}	
type	basicService	struct{}	
func	(s	basicService)	Sum(_	context.Context,	a,	b	int)	(int,	error)	{	
	 if	a	==	0	&&	b	==	0	{	
	 	 return	0,	ErrTwoZeroes	
	 }	
	 if	(b	>	0	&&	a	>	(intMax-b))	||	(b	<	0	&&	a	<	(intMin-b))	{	
	 	 return	0,	ErrIntOverflow	
	 }	
	 return	a	+	b,	nil	
}	
//	Concat	implements	Service.	
func	(s	basicService)	Concat(_	context.Context,	a,	b	string)	(string,	error)	{	
	 if	len(a)+len(b)	>	maxLen	{	
	 	 return	"",	ErrMaxSizeExceeded	
	 }	
	 return	a	+	b,	nil	
}
Middlewares
• Bentuk konkret dari konsep separation of concern dengan
menggunakan decorator pattern.

• Membungkus endpoint atau services untuk menambah
fungsionalitas yang generik, misal logging, rate limiting
atau distributed tracing.
• Ada 2 tipe, Endpoint Middleware & Endpoint Service.
Endpoint Middleware
func	InstrumentingMiddleware(duration	metrics.Histogram)	endpoint.Middleware	{	
		return	func(next	endpoint.Endpoint)	endpoint.Endpoint	{	
				return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
						defer	func(begin	time.Time)	{	
								duration.With("success",	fmt.Sprint(err	==	nil)).Observe(time.Since(begin).Seconds())	
						}(time.Now())	
						return	next(ctx,	request)	
				}	
		}	
}
func	InstrumentingMiddleware(duration	metrics.Histogram)	endpoint.Middleware	{	
		return	func(next	endpoint.Endpoint)	endpoint.Endpoint	{	
				return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
						defer	func(begin	time.Time)	{	
								duration.With("success",	fmt.Sprint(err	==	nil)).Observe(time.Since(begin).Seconds())	
						}(time.Now())	
						return	next(ctx,	request)	
				}	
		}	
}	
Endpoint Middleware
func	InstrumentingMiddleware(duration	metrics.Histogram)	endpoint.Middleware	{	
		return	func(next	endpoint.Endpoint)	endpoint.Endpoint	{	
				return	func(ctx	context.Context,	request	interface{})	(response	interface{},	err	error)	{	
						defer	func(begin	time.Time)	{	
								duration.With("success",	fmt.Sprint(err	==	nil)).Observe(time.Since(begin).Seconds())	
						}(time.Now())	
						return	next(ctx,	request)	
				}	
		}	
}	
Endpoint Middleware
//	Middleware	describes	a	service	(as	opposed	to	endpoint)	middleware.	
type	Middleware	func(Service)	Service	
//	LoggingMiddleware	takes	a	logger	as	a	dependency	
//	and	returns	a	service	Middleware.	
func	LoggingMiddleware(logger	log.Logger)	Middleware	{	
		return	func(next	Service)	Service	{	
				return	loggingMiddleware{logger,	next}	
		}	
}	
type	loggingMiddleware	struct	{	
		logger	log.Logger	
		next			Service	
}	
func	(mw	loggingMiddleware)	Sum(ctx	context.Context,	a,	b	int)	(v	int,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Sum",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Sum(ctx,	a,	b)	
}	
func	(mw	loggingMiddleware)	Concat(ctx	context.Context,	a,	b	string)	(v	string,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Concat",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Concat(ctx,	a,	b)	
}
//	Middleware	describes	a	service	(as	opposed	to	endpoint)	middleware.	
type	Middleware	func(Service)	Service	
//	LoggingMiddleware	takes	a	logger	as	a	dependency	
//	and	returns	a	service	Middleware.	
func	LoggingMiddleware(logger	log.Logger)	Middleware	{	
		return	func(next	Service)	Service	{	
				return	loggingMiddleware{logger,	next}	
		}	
}	
type	loggingMiddleware	struct	{	
		logger	log.Logger	
		next			Service	
}	
func	(mw	loggingMiddleware)	Sum(ctx	context.Context,	a,	b	int)	(v	int,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Sum",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Sum(ctx,	a,	b)	
}	
func	(mw	loggingMiddleware)	Concat(ctx	context.Context,	a,	b	string)	(v	string,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Concat",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Concat(ctx,	a,	b)	
}
//	Middleware	describes	a	service	(as	opposed	to	endpoint)	middleware.	
type	Middleware	func(Service)	Service	
//	LoggingMiddleware	takes	a	logger	as	a	dependency	
//	and	returns	a	service	Middleware.	
func	LoggingMiddleware(logger	log.Logger)	Middleware	{	
		return	func(next	Service)	Service	{	
				return	loggingMiddleware{logger,	next}	
		}	
}	
type	loggingMiddleware	struct	{	
		logger	log.Logger	
		next			Service	
}	
func	(mw	loggingMiddleware)	Sum(ctx	context.Context,	a,	b	int)	(v	int,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Sum",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Sum(ctx,	a,	b)	
}	
func	(mw	loggingMiddleware)	Concat(ctx	context.Context,	a,	b	string)	(v	string,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Concat",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Concat(ctx,	a,	b)	
}
//	Middleware	describes	a	service	(as	opposed	to	endpoint)	middleware.	
type	Middleware	func(Service)	Service	
//	LoggingMiddleware	takes	a	logger	as	a	dependency	
//	and	returns	a	service	Middleware.	
func	LoggingMiddleware(logger	log.Logger)	Middleware	{	
		return	func(next	Service)	Service	{	
				return	loggingMiddleware{logger,	next}	
		}	
}	
type	loggingMiddleware	struct	{	
		logger	log.Logger	
		next			Service	
}	
func	(mw	loggingMiddleware)	Sum(ctx	context.Context,	a,	b	int)	(v	int,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Sum",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Sum(ctx,	a,	b)	
}	
func	(mw	loggingMiddleware)	Concat(ctx	context.Context,	a,	b	string)	(v	string,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Concat",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Concat(ctx,	a,	b)	
}
//	Middleware	describes	a	service	(as	opposed	to	endpoint)	middleware.	
type	Middleware	func(Service)	Service	
//	LoggingMiddleware	takes	a	logger	as	a	dependency	
//	and	returns	a	service	Middleware.	
func	LoggingMiddleware(logger	log.Logger)	Middleware	{	
		return	func(next	Service)	Service	{	
				return	loggingMiddleware{logger,	next}	
		}	
}	
type	loggingMiddleware	struct	{	
		logger	log.Logger	
		next			Service	
}	
func	(mw	loggingMiddleware)	Sum(ctx	context.Context,	a,	b	int)	(v	int,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Sum",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Sum(ctx,	a,	b)	
}	
func	(mw	loggingMiddleware)	Concat(ctx	context.Context,	a,	b	string)	(v	string,	err	error)	{	
		defer	func()	{	
				mw.logger.Log("method",	"Concat",	"a",	a,	"b",	b,	"v",	v,	"err",	err)	
		}()	
		return	mw.next.Concat(ctx,	a,	b)	
}
main.go
		//	Build	the	layers	of	the	service	"onion"	from	the	inside	out.	First,	the	
		//	business	logic	service;	then,	the	set	of	endpoints	that	wrap	the	service;	
		//	and	finally,	a	series	of	concrete	transport	adapters.	The	adapters,	like	
		//	the	HTTP	handler	or	the	gRPC	server,	are	the	bridge	between	Go	kit	and	
		//	the	interfaces	that	the	transports	expect.	Note	that	we're	not	binding	
		//	them	to	ports	or	anything	yet;	we'll	do	that	next.	
		var	(	
				service								=	addservice.New(logger,	ints,	chars)	
				endpoints						=	addendpoint.New(service,	logger,	duration,	tracer,	zipkinTracer)	
				httpHandler				=	addtransport.NewHTTPHandler(endpoints,	tracer,	zipkinTracer,	logger)	
				grpcServer					=	addtransport.NewGRPCServer(endpoints,	tracer,	zipkinTracer,	logger)	
	)
main.go
		var	g	group.Group	
		{	
				//	The	HTTP	listener	mounts	the	Go	kit	HTTP	handler	we	created.	
				httpListener,	err	:=	net.Listen("tcp",	*httpAddr)	
				if	err	!=	nil	{	
						logger.Log("transport",	"HTTP",	"during",	"Listen",	"err",	err)	
						os.Exit(1)	
				}	
				g.Add(func()	error	{	
						logger.Log("transport",	"HTTP",	"addr",	*httpAddr)	
						return	http.Serve(httpListener,	httpHandler)	
				},	func(error)	{	
						httpListener.Close()	
				})	
		}	
		logger.Log("exit",	g.Run())
Sejauh ini…
• Cenderung susah dipahami diawal.

• Verbose.

• Sangat gampang ketika ada perubahan requirement.

• Banyak dukungan library.

• Pakai apa yang kita butuhkan.
Terus..
• Implementasi core business logic di service layer, dengan
mendefinisikan interface plus dengan implementasi konkretnya.

• Buat service middleware, disini biasanya utk beberapa fungsi
tambahan, seperti analytics atau logging - intinya yang dibutuhkan
oleh domain business. 

• Untuk middleware yang agnostik terhadap domain business, misal
circuit breaker, rate limiter, dll bisa dibuat di endpoint middleware.
• Buat Transport Layer dengan semua decoder/encoder-nya.

• Satukan mereka ❤ di Endpoint Layer.
Referensi
• go-kit.io

• https://martinfowler.com/articles/microservices.html

• https://blog.cleancoder.com/uncle-bob/2012/08/13/the-
clean-architecture.html

• https://docs.microsoft.com/en-us/azure/service-fabric/
service-fabric-overview-microservices
Terima kasih!

More Related Content

Similar to Microservices dengan Go-kit: Go-kit untuk membangun microservices dengan Go

A microservice architecture based on golang
A microservice architecture based on golangA microservice architecture based on golang
A microservice architecture based on golangGianfranco Reppucci
 
Cytoscape CI Chapter 2
Cytoscape CI Chapter 2Cytoscape CI Chapter 2
Cytoscape CI Chapter 2bdemchak
 
Web services - A Practical Approach
Web services - A Practical ApproachWeb services - A Practical Approach
Web services - A Practical ApproachMadhaiyan Muthu
 
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox BlueHat Security Conference
 
Processes and Telecom APIs
Processes and Telecom APIsProcesses and Telecom APIs
Processes and Telecom APIsAlan Quayle
 
Biztalk ESB Toolkit Introduction
Biztalk ESB Toolkit IntroductionBiztalk ESB Toolkit Introduction
Biztalk ESB Toolkit IntroductionSaffi Ali
 
CocoaConf: The Language of Mobile Software is APIs
CocoaConf: The Language of Mobile Software is APIsCocoaConf: The Language of Mobile Software is APIs
CocoaConf: The Language of Mobile Software is APIsTim Burks
 
The Ongoing Democratization of Robotics Development
The Ongoing Democratization of Robotics DevelopmentThe Ongoing Democratization of Robotics Development
The Ongoing Democratization of Robotics Developmentukdpe
 
Polyakov how i will break your enterprise. esb security and more
Polyakov   how i will break your enterprise. esb security and morePolyakov   how i will break your enterprise. esb security and more
Polyakov how i will break your enterprise. esb security and moreDefconRussia
 
Exploring Data Integration Capabilities of the WSO2 Platform
Exploring Data Integration Capabilities of the WSO2 PlatformExploring Data Integration Capabilities of the WSO2 Platform
Exploring Data Integration Capabilities of the WSO2 PlatformWSO2
 
DWX2018 IoT lecture
DWX2018 IoT lectureDWX2018 IoT lecture
DWX2018 IoT lectureAlon Fliess
 
Neotys PAC 2018 - Jonathon Wright
Neotys PAC 2018 - Jonathon WrightNeotys PAC 2018 - Jonathon Wright
Neotys PAC 2018 - Jonathon WrightNeotys_Partner
 
What is a Service Mesh and what can it do for your Microservices
What is a Service Mesh and what can it do for your MicroservicesWhat is a Service Mesh and what can it do for your Microservices
What is a Service Mesh and what can it do for your MicroservicesMatt Turner
 
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18CodeOps Technologies LLP
 
StrongLoop Overview
StrongLoop OverviewStrongLoop Overview
StrongLoop OverviewShubhra Kar
 
The Hitchhiker’s Guide to Hybrid Connectivity
The Hitchhiker’s Guide to Hybrid ConnectivityThe Hitchhiker’s Guide to Hybrid Connectivity
The Hitchhiker’s Guide to Hybrid ConnectivityBizTalk360
 
.NET Core Today and Tomorrow
.NET Core Today and Tomorrow.NET Core Today and Tomorrow
.NET Core Today and TomorrowJon Galloway
 

Similar to Microservices dengan Go-kit: Go-kit untuk membangun microservices dengan Go (20)

A microservice architecture based on golang
A microservice architecture based on golangA microservice architecture based on golang
A microservice architecture based on golang
 
Cytoscape CI Chapter 2
Cytoscape CI Chapter 2Cytoscape CI Chapter 2
Cytoscape CI Chapter 2
 
Web services - A Practical Approach
Web services - A Practical ApproachWeb services - A Practical Approach
Web services - A Practical Approach
 
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox
BlueHat v17 || Corrupting Memory in Microsoft Office Protected-View Sandbox
 
Processes and Telecom APIs
Processes and Telecom APIsProcesses and Telecom APIs
Processes and Telecom APIs
 
Biztalk ESB Toolkit Introduction
Biztalk ESB Toolkit IntroductionBiztalk ESB Toolkit Introduction
Biztalk ESB Toolkit Introduction
 
CocoaConf: The Language of Mobile Software is APIs
CocoaConf: The Language of Mobile Software is APIsCocoaConf: The Language of Mobile Software is APIs
CocoaConf: The Language of Mobile Software is APIs
 
The Ongoing Democratization of Robotics Development
The Ongoing Democratization of Robotics DevelopmentThe Ongoing Democratization of Robotics Development
The Ongoing Democratization of Robotics Development
 
Connect Bridge
Connect BridgeConnect Bridge
Connect Bridge
 
Polyakov how i will break your enterprise. esb security and more
Polyakov   how i will break your enterprise. esb security and morePolyakov   how i will break your enterprise. esb security and more
Polyakov how i will break your enterprise. esb security and more
 
Node azure
Node azureNode azure
Node azure
 
Exploring Data Integration Capabilities of the WSO2 Platform
Exploring Data Integration Capabilities of the WSO2 PlatformExploring Data Integration Capabilities of the WSO2 Platform
Exploring Data Integration Capabilities of the WSO2 Platform
 
DWX2018 IoT lecture
DWX2018 IoT lectureDWX2018 IoT lecture
DWX2018 IoT lecture
 
Neotys PAC 2018 - Jonathon Wright
Neotys PAC 2018 - Jonathon WrightNeotys PAC 2018 - Jonathon Wright
Neotys PAC 2018 - Jonathon Wright
 
What is a Service Mesh and what can it do for your Microservices
What is a Service Mesh and what can it do for your MicroservicesWhat is a Service Mesh and what can it do for your Microservices
What is a Service Mesh and what can it do for your Microservices
 
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18
Make Java Microservices Resilient with Istio - Mangesh - IBM - CC18
 
StrongLoop Overview
StrongLoop OverviewStrongLoop Overview
StrongLoop Overview
 
The Hitchhiker’s Guide to Hybrid Connectivity
The Hitchhiker’s Guide to Hybrid ConnectivityThe Hitchhiker’s Guide to Hybrid Connectivity
The Hitchhiker’s Guide to Hybrid Connectivity
 
.NET Core Today and Tomorrow
.NET Core Today and Tomorrow.NET Core Today and Tomorrow
.NET Core Today and Tomorrow
 
Rsockets ofa12
Rsockets ofa12Rsockets ofa12
Rsockets ofa12
 

Recently uploaded

Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfRTS corp
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecturerahul_net
 
Zer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfZer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfmaor17
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLionel Briand
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITmanoharjgpsolutions
 
Osi security architecture in network.pptx
Osi security architecture in network.pptxOsi security architecture in network.pptx
Osi security architecture in network.pptxVinzoCenzo
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingShane Coughlan
 
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jGraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jNeo4j
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorTier1 app
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesKrzysztofKkol1
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxRTS corp
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsJean Silva
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonApplitools
 
eSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolseSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolsosttopstonverter
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slidesvaideheekore1
 
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdfAndrey Devyatkin
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingShane Coughlan
 

Recently uploaded (20)

Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdfEnhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
Enhancing Supply Chain Visibility with Cargo Cloud Solutions.pdf
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecture
 
Zer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfZer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdf
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and Repair
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh IT
 
Osi security architecture in network.pptx
Osi security architecture in network.pptxOsi security architecture in network.pptx
Osi security architecture in network.pptx
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
 
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jGraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryError
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
 
Strategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero resultsStrategies for using alternative queries to mitigate zero results
Strategies for using alternative queries to mitigate zero results
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
 
eSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration toolseSoftTools IMAP Backup Software and migration tools
eSoftTools IMAP Backup Software and migration tools
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slides
 
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf
2024-04-09 - From Complexity to Clarity - AWS Summit AMS.pdf
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
 

Microservices dengan Go-kit: Go-kit untuk membangun microservices dengan Go