Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
54: Designing Microservices in Go
By the time you're designing microservices, you've likely realized that the hardest part isn't the coding—it's the boundaries. In Go, we have a lot of tools to make these services fast, but it's easy to accidentally build a "distributed monolith" where everything is coupled, and one crash takes down the whole fleet. Let's look at how to avoid that using a real-world scenario: an e-commerce system with an Order Service and a Catalog Service.
How should I actually organize my folders so the project doesn't become a mess?
I see a lot of developers just throw everything into a /models or /utils folder. Please, don't do that. In Go, we want to organize by responsibility and visibility. I highly recommend the /internal pattern. Anything inside an internal directory cannot be imported by other modules, which is a lifesaver when you're trying to ensure the Order Service doesn't accidentally start relying on the Catalog Service's private logic.
Here is a layout I've used in production that scales well:
/order-service /cmd /server main.go // Entry point: wire up dependencies here /internal /order service.go // Business logic repository.go // Database interactions handler.go // HTTP/gRPC transport logic /catalogclient // Logic to talk to the Catalog Service client.go /pkg /api // Public types/interfaces if other services need them go.modThe key here is that
main.gois just the "glue." It initializes the database, creates theCatalogClient, and passes it into theOrderService. This makes unit testing a breeze because you can just swap the real client for a mock.Should I be using REST/JSON or gRPC for service-to-service calls?
If you're building a public API for a frontend, stick with REST. But for internal communication between your Go services? Use gRPC. I'll be blunt: sending JSON back and forth between two services you control is a waste of CPU and bandwidth. gRPC uses Protocol Buffers, which are binary and strongly typed. You get a generated client and server, meaning you don't have to manually write
http.Getcalls and hope the JSON keys match.Imagine your
Order Serviceneeds to check if a product is in stock. Instead of parsing a JSON string, you define it once in a.protofile:syntax = "proto3"; service CatalogService { rpc GetProductStock (StockRequest) returns (StockResponse); } message StockRequest { string product_id = 1; } message StockResponse { int32 quantity = 1; bool is_available = 2; }Go's
protoccompiler then generates the Go structs and methods for you. It's faster, it catches type errors at compile time, and it documents the API automatically.What happens when the Catalog Service goes down? Does the Order Service just hang?
This is where most people trip up. If your
Order Servicemakes a synchronous call to theCatalog Serviceand that service is lagging, your order requests will pile up, memory will spike, and eventually, yourOrder Servicewill crash too. This is a cascading failure.You must use
context.Contextwith a timeout for every single external call. Never make a network request without a deadline. I also suggest a simple "circuit breaker" pattern—if theCatalog Servicefails five times in a row, stop trying to call it for 30 seconds and just return a "Service Temporarily Unavailable" error immediately.Here is how you should wrap your client calls:
func (c *CatalogClient) CheckStock(ctx context.Context, id string) (int, error) { // Create a derived context that expires in 2 seconds ctx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() // Pass that context into the gRPC or HTTP call resp, err := c.grpcClient.GetProductStock(ctx, &pb.StockRequest{ProductId: id}) if err != nil { if errors.Is(ctx.Err(), context.DeadlineExceeded) { return 0, fmt.Errorf("catalog service timed out") } return 0, err } return int(resp.Quantity), nil }By doing this, you're protecting your own service. It's better to tell a user "We can't verify stock right now" than to let your entire infrastructure melt down because one small service is having a bad day.
📋 Practical Task
Exercise: Implementing a Resilient Product-Stock Checker
You are tasked with adding a safety layer to the Order Service. Currently, it calls the Catalog Service without any timeouts, causing the system to hang when the network is slow.
Your Goal: Modify the provided StockClient implementation to ensure that no request to the Catalog Service takes longer than 500 milliseconds. If the timeout is reached, the function should return a custom error: "catalog_timeout".
Requirements:
- Use
context.WithTimeoutto wrap the existing request. - Ensure
cancel()is called to prevent context leaks. - Correctly identify if the error returned by the client is a
context.DeadlineExceedederror. - The return value should be
(0, errors.New("catalog_timeout"))upon expiration.
There are no comments for now.