
[{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/article/","section":"Articles","summary":"","title":"Articles","type":"article"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/golang/","section":"Tags","summary":"","title":"Golang","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/llm/","section":"Tags","summary":"","title":"Llm","type":"tags"},{"content":"The previous two articles in this series covered the Chat Completions API — how to set up a client, maintain conversation history manually, call external tools, and control output structure with response_format. That API gives you full control and a clear mental model of what goes over the wire. This article covers the other primary OpenAI interface: the Responses API.\nThe Responses API moves conversation state from the client to OpenAI\u0026rsquo;s servers. You no longer maintain a history slice and re-send it with every call. Instead, you track a response ID and pass it back on the next request. That is a meaningful shift for agent-oriented applications — less maintenance, but also less transparency. Understanding the trade-offs between the two APIs is worth doing before choosing which one to build on.\nResponses API # OpenAI introduced the Responses API in 2025, positioning it as the foundation for building agents. The Chat Completions API is stateless — every request must carry the full conversation history, and the client owns that state entirely. The Responses API inverts this: conversation state lives on OpenAI\u0026rsquo;s servers, and you reference previous turns by ID rather than re-sending them.\nBoth APIs give you access to the same underlying models and tool-calling mechanics. The difference is where the orchestration responsibility sits. The table below, first introduced in the Chat Completions API article, summarizes the trade-offs:\nFeature Chat Completions API Responses API Conversation state Client-managed Server-managed History management Manual — sent with every request Automatic Tool support Manual function calling Built-in tools (web search, code interpreter) Streaming Yes Yes Control Full Limited Vendor coupling Low Higher Best for Custom agents, full control Rapid prototyping, built-in tooling The Chat Completions API is the right default when you want to control exactly what the model sees and when you need portability across providers. The Responses API reduces boilerplate and fits well when you want to prototype quickly or lean on OpenAI\u0026rsquo;s managed tooling. In this article we build the same conversational agent we built before — but with the Responses API driving state management.\nFirst AI agent # The openai-go SDK covers both APIs under one package. The same client initialization you used for Chat Completions works here. To call the API, you need a secret key from platform.openai.com — navigate to the API Keys section, generate a key, and store it in an environment variable.\npackage main import ( // ... \u0026#34;github.com/openai/openai-go\u0026#34; \u0026#34;github.com/openai/openai-go/option\u0026#34; ) var previousResponseID string var client openai.Client func main() { apiKey := os.Getenv(\u0026#34;OPENAI_API_KEY\u0026#34;) if apiKey == \u0026#34;\u0026#34; { fmt.Fprintln(os.Stderr, \u0026#34;error: OPENAI_API_KEY environment variable is not set\u0026#34;) os.Exit(1) } client = openai.NewClient(option.WithAPIKey(apiKey)) } The client is created the same way as before — API key from the environment, exit immediately if it is missing. The difference is what comes next. Instead of a history slice, we declare a previousResponseID string. The Responses API handles conversation state on its side; all we track is the ID of the last response so we can tell the API what the previous turn was. When previousResponseID is empty, the API treats the request as the start of a new conversation.\nimport ( \u0026#34;bufio\u0026#34; // ... ) func talkToAgent(userInput string) (string, error) { return \u0026#34;\u0026#34;, nil } scanner := bufio.NewScanner(os.Stdin) fmt.Println(\u0026#34;AI assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit.\u0026#34;) fmt.Println() for { fmt.Print(\u0026#34;Human: \u0026#34;) if !scanner.Scan() { break } input := strings.TrimSpace(scanner.Text()) if input == \u0026#34;\u0026#34; { continue } if input == \u0026#34;exit\u0026#34; { break } text, err := talkToAgent(input) if err != nil { fmt.Fprintf(os.Stderr, \u0026#34;Error: %v\\n\u0026#34;, err) continue } fmt.Printf(\u0026#34;Agent: %s\\n\\n\u0026#34;, text) } Here bufio.Scanner reads user input line by line from standard input. Each non-empty, non-exit line is passed directly to talkToAgent as a string argument — compare this to the Chat Completions version, where we first appended the input to the history slice before calling the function. With the Responses API, the input goes straight to the function and the API takes care of threading it into the ongoing conversation.\nfunc talkToAgent(userInput string) (string, error) { params := responses.ResponseNewParams{ Model: openai.ChatModelGPT4_1Mini, Instructions: openai.String( \u0026#34;You are a helpful assistant.\u0026#34;, ), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String(userInput), }, } if previousResponseID != \u0026#34;\u0026#34; { params.PreviousResponseID = openai.String(previousResponseID) } resp, err := client.Responses.New(context.Background(), params) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } previousResponseID = resp.ID for _, item := range resp.Output { switch item.Type { case \u0026#34;message\u0026#34;: msg := item.AsMessage() for _, content := range msg.Content { if content.Type == \u0026#34;output_text\u0026#34; { return content.AsOutputText().Text, nil } } } } return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;unexpected response: no text output and no tool calls\u0026#34;) } Building the request starts with responses.ResponseNewParams. The Instructions field replaces the system message from Chat Completions — same concept, different field name. The user\u0026rsquo;s input goes into Input as a plain string. If previousResponseID is set, we attach it to the params; this is what connects the request to the previous turn on OpenAI\u0026rsquo;s side. Without it, the model has no history of the conversation.\nAfter the call completes, we immediately update previousResponseID with the ID from the response. This is the entirety of the state management — one string, updated on every turn. The response output is a typed list of items. We iterate and look for a message item containing an output_text block; that is where the model\u0026rsquo;s text response is. The structure is more nested than the Chat Completions response, but the pattern is consistent once you have seen it once.\nAI assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit. Human: hi Agent: Hello! How can I assist you today? Human: what is my name? Agent: I don\u0026#39;t know your name yet. Could you please tell me what it is? Human: my name is Marko Agent: Nice to meet you, Marko! How can I help you today? Human: what is my name? Agent: Your name is Marko. How can I assist you further? The agent remembers the user\u0026rsquo;s name across turns — because previousResponseID links each request to the prior one and OpenAI reconstructs the context on its side. This is the core value of the Responses API: multi-turn memory without any client-side history management.\nFirst tool integration # The Responses API supports the same tool-calling pattern as Chat Completions. To demonstrate, consider what happens when you ask the agent a question it cannot answer without real-world data:\nHuman: what is the current time in Frankfurt? Agent: I don\u0026#39;t have access to real-time data. However, you can check the current time in Frankfurt by searching \u0026#34;current time in Frankfurt\u0026#34; on a search engine or by using a world clock app. Frankfurt is in the Central European Time (CET) zone, which is UTC+1 during standard time and UTC+2 during daylight saving time (typically from the last Sunday in March to the last Sunday in October). The model knows about time zones — it just does not know what time it is right now. Tools exist precisely to fill this gap: they let the model delegate specific calls to external functions and incorporate the results into its response. The process works in three steps. First, you send the model a list of tool definitions — names, descriptions, and parameter schemas. The model reads those definitions and decides whether it needs to invoke one before it can answer. If it does, it responds not with a message but with a list of tool calls, each containing the tool name and the arguments it wants to pass. You execute those calls on your side, then send a new request to the model with the results. The model may request additional tool calls or produce a final answer. This loop continues until the model has everything it needs.\nThe first step is a plain Go function that retrieves the current time in a given timezone:\nfunc getCurrentTime(timezone string) (string, error) { location, err := time.LoadLocation(timezone) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to load location: %w\u0026#34;, err) } now := time.Now().In(location) return now.Format(\u0026#34;2006-01-02 15:04:05\u0026#34;), nil } Here time.LoadLocation resolves an IANA timezone name like Europe/Berlin to a *time.Location. time.Now().In(location) returns the current instant in that timezone, and we format it with Go\u0026rsquo;s reference time. The function is deliberately simple — it does one thing and returns a string. Tool functions do not need to be complex; they need to be correct and fast.\nNext, we define the tool and update talkToAgent to include it in the request:\nimport ( // ... \u0026#34;github.com/openai/openai-go/responses\u0026#34;\t) var getTimeToolDefinition = responses.ToolUnionParam{ OfFunction: \u0026amp;responses.FunctionToolParam{ Name: \u0026#34;get_time\u0026#34;, Description: openai.String(\u0026#34;Fetch the current time in a given timezone.\u0026#34;), Parameters: openai.FunctionParameters{ \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: map[string]interface{}{ \u0026#34;timezone\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Tne international timezone name, e.g. America/Los_Angeles.\u0026#34;, }, }, \u0026#34;required\u0026#34;: []string{\u0026#34;timezone\u0026#34;}, \u0026#34;additionalProperties\u0026#34;: false, }, }, } func talkToAgent(userInput string) (string, error) { params := responses.ResponseNewParams{ Model: openai.ChatModelGPT4oMini, Instructions: openai.String( \u0026#34;You are a helpful time assistant. \u0026#34; + \u0026#34;When asked about time at particular locations, \u0026#34; + \u0026#34;always use the get_time tool to get current time in a given timezone. \u0026#34; + \u0026#34;Never guess the time.\u0026#34;, ), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String(userInput), }, Tools: []responses.ToolUnionParam{ getTimeToolDefinition, }, } if previousResponseID != \u0026#34;\u0026#34; { params.PreviousResponseID = openai.String(previousResponseID) } for { resp, err := client.Responses.New(context.Background(), params) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } previousResponseID = resp.ID // ... } } The tool definition is a responses.ToolUnionParam wrapping a responses.FunctionToolParam. The Parameters field is a standard JSON Schema object: it declares that the tool accepts a single required timezone string, with additionalProperties: false to prevent the model from passing fields we did not define. The system prompt is updated to instruct the model to always use get_time for time queries and never guess — without this instruction, the model may produce a plausible-sounding but stale answer from its training data.\nThe API call is now wrapped in a for loop. This is essential: when the model decides to call a tool, it does not return a text message — it returns a list of function calls. We execute those, send the results back, and the loop continues. A single turn can involve multiple round-trips between the application and the model before a final answer is produced.\nfunc callTool(call responses.ResponseFunctionToolCall) (string, error) { // ... } func talkToAgent(userInput string) (string, error) { // ... for { resp, err := client.Responses.New(context.Background(), params) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } previousResponseID = resp.ID var toolResultItems []responses.ResponseInputItemUnionParam hasToolCalls := false for _, item := range resp.Output { switch item.Type { case \u0026#34;function_call\u0026#34;: hasToolCalls = true toolCall := item.AsFunctionCall() fmt.Printf(\u0026#34;[tool call: %s(%s)]\\n\u0026#34;, toolCall.Name, toolCall.Arguments) result, err := callTool(toolCall) if err != nil { result = fmt.Sprintf(\u0026#34;error: %s\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;[tool result: %s]\\n\u0026#34;, result) toolResultItems = append(toolResultItems, responses.ResponseInputItemParamOfFunctionCallOutput(toolCall.CallID, result)) case \u0026#34;message\u0026#34;: msg := item.AsMessage() for _, content := range msg.Content { if content.Type == \u0026#34;output_text\u0026#34; { return content.AsOutputText().Text, nil } } } } if !hasToolCalls { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;unexpected response: no text output and no tool calls\u0026#34;) } params = responses.ResponseNewParams{ Model: openai.ChatModelGPT4oMini, PreviousResponseID: openai.String(previousResponseID), Input: responses.ResponseNewParamsInputUnion{ OfInputItemList: toolResultItems, }, Tools: []responses.ToolUnionParam{ getTimeToolDefinition, }, } } } Each output item is inspected by type. A message item means the model has produced a final answer — we return it immediately. A function_call item means the model wants to invoke a tool. We call callTool with the tool call details, collect the result, and append it to toolResultItems using ResponseInputItemParamOfFunctionCallOutput. If the response contained tool calls but no message, we build a new ResponseNewParams with the tool results as input and loop again. The Instructions field is deliberately omitted from this follow-up request — only PreviousResponseID and the tool results are needed, since the model already has the conversation context from the prior response.\ntype timeArgs struct { Timezone string `json:\u0026#34;timezone\u0026#34;` } type timeResult struct { Time string `json:\u0026#34;time\u0026#34;` } func callTool(call responses.ResponseFunctionToolCall) (string, error) { switch call.Name { case \u0026#34;get_time\u0026#34;: var args timeArgs if err := json.Unmarshal([]byte(call.Arguments), \u0026amp;args); err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to parse tool arguments: %w\u0026#34;, err) } currentTime, err := getCurrentTime(args.Timezone) if err != nil { errMsg, _ := json.Marshal(map[string]string{\u0026#34;error\u0026#34;: err.Error()}) return string(errMsg), nil } result := timeResult{ Time: currentTime, } out, err := json.Marshal(result) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to marshal tool result: %w\u0026#34;, err) } return string(out), nil default: return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;unknown tool: %q\u0026#34;, call.Name) } } Now, callTool is a dispatcher: it switches on the tool name and routes to the appropriate Go function. The model returns arguments as a JSON string, so we unmarshal into a typed struct — timeArgs in this case — before calling getCurrentTime. The result is marshaled back to JSON and returned as a string. This JSON contract is what the Responses API expects for tool outputs. Notice that errors from getCurrentTime are also returned as JSON rather than propagated as Go errors — this allows the model to read the error and respond to the user intelligently rather than crashing the loop.\nThe model is smart enough to map a natural-language question like \u0026ldquo;what is the time in Frankfurt?\u0026rdquo; to the IANA timezone Europe/Berlin — it knows the relationship between city names and timezone identifiers from its training data. The tool definition only needed to describe the parameter; the model handles the resolution.\nTime assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit. Human: what is the time in Frankfurt? [tool call: get_time({\u0026#34;timezone\u0026#34;:\u0026#34;Europe/Berlin\u0026#34;})] [tool result: {\u0026#34;time\u0026#34;:\u0026#34;2026-03-18 17:13:50\u0026#34;}] Agent: The current time in Frankfurt is 17:13 (5:13 PM) on March 18, 2026. The full source is available at llm-and-golang-examples.\nConclusion # The Responses API trades control for convenience. Server-side state management removes the history-maintenance boilerplate from the Chat Completions API, and the tool-calling pattern maps cleanly onto ordinary Go functions. The cost of that convenience is tighter coupling to OpenAI\u0026rsquo;s infrastructure and less visibility into exactly what the model receives on each turn. For production systems where observability and provider flexibility matter, the Chat Completions API remains the more defensible choice. For rapid agent prototyping, the Responses API is the faster path. Knowing both gives you the option to choose based on actual requirements rather than default habit.\nUseful Resources # Responses API reference openai-go SDK Full source example IANA Time Zone Database bufio.Scanner ","date":"19 March 2026","externalUrl":null,"permalink":"/article/golang/llm-and-golang-gpt-responses/","section":"Articles","summary":"The previous two articles in this series covered the Chat Completions API — how to set up a client, maintain conversation history manually, call external tools, and control output structure with response_format. That API gives you full control and a clear mental model of what goes over the wire. This article covers the other primary OpenAI interface: the Responses API.\nThe Responses API moves conversation state from the client to OpenAI’s servers. You no longer maintain a history slice and re-send it with every call. Instead, you track a response ID and pass it back on the next request. That is a meaningful shift for agent-oriented applications — less maintenance, but also less transparency. Understanding the trade-offs between the two APIs is worth doing before choosing which one to build on.\nResponses API # OpenAI introduced the Responses API in 2025, positioning it as the foundation for building agents. The Chat Completions API is stateless — every request must carry the full conversation history, and the client owns that state entirely. The Responses API inverts this: conversation state lives on OpenAI’s servers, and you reference previous turns by ID rather than re-sending them.\nBoth APIs give you access to the same underlying models and tool-calling mechanics. The difference is where the orchestration responsibility sits. The table below, first introduced in the Chat Completions API article, summarizes the trade-offs:\nFeature Chat Completions API Responses API Conversation state Client-managed Server-managed History management Manual — sent with every request Automatic Tool support Manual function calling Built-in tools (web search, code interpreter) Streaming Yes Yes Control Full Limited Vendor coupling Low Higher Best for Custom agents, full control Rapid prototyping, built-in tooling The Chat Completions API is the right default when you want to control exactly what the model sees and when you need portability across providers. The Responses API reduces boilerplate and fits well when you want to prototype quickly or lean on OpenAI’s managed tooling. In this article we build the same conversational agent we built before — but with the Responses API driving state management.\nFirst AI agent # The openai-go SDK covers both APIs under one package. The same client initialization you used for Chat Completions works here. To call the API, you need a secret key from platform.openai.com — navigate to the API Keys section, generate a key, and store it in an environment variable.\n","title":"LLM and Go: OpenAI integration via Responses API","type":"article"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/series/llm-and-golang/","section":"Series","summary":"","title":"LLM and Golang","type":"series"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/","section":"Ompluscator's Blog","summary":"","title":"Ompluscator's Blog","type":"page"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/openai/","section":"Tags","summary":"","title":"Openai","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"19 March 2026","externalUrl":null,"permalink":"/tags/tutorial/","section":"Tags","summary":"","title":"Tutorial","type":"tags"},{"content":"","date":"6 March 2026","externalUrl":null,"permalink":"/series/llm-and-go/","section":"Series","summary":"","title":"LLM and Go","type":"series"},{"content":"In the previous article I covered the fundamentals of the Chat Completions API: setting up a client, maintaining conversation history, and integrating tools. That was enough to build a working conversational agent. This article goes a level deeper — into the API parameters that shape what the model returns and how it thinks.\nTwo parameters stand out as particularly useful in production: response_format and reasoning_effort. The first gives you control over the structure of the model\u0026rsquo;s output. The second controls how much the model reasons before responding — which turns out to matter more than you might expect once you start caring about latency and cost.\nChat Completions API details # The Chat Completions API endpoint accepts a rich set of parameters. Most have sensible defaults and you will rarely touch them, but understanding what is available saves you from reaching for workarounds that already exist in the API. The table below covers the current non-deprecated parameters from the API reference:\nParameter Type Description model string ID of the model to use messages array Conversation history as an ordered list of messages response_format object Output format: text, json_object, or json_schema reasoning_effort string Reasoning intensity for reasoning models: low, medium, high temperature number Sampling temperature from 0 to 2; higher values produce more random output top_p number Alternative to temperature; nucleus sampling probability mass max_completion_tokens integer Maximum tokens the model may generate in the response n integer Number of completion choices to return stream boolean Stream partial responses as server-sent events stop string/array Sequences at which the API stops generating presence_penalty number Penalises new tokens based on whether they appear in the text so far frequency_penalty number Penalises new tokens based on their frequency in the text so far tools array List of tools (functions) the model may call tool_choice string/object Controls which tool the model calls seed integer Seed for deterministic sampling user string Unique identifier for the end user In this article we focus on response_format and reasoning_effort — two parameters with a direct, visible impact on production systems.\nInformation extraction with response_format # The response_format parameter controls how the model structures its output. The default is plain text. Setting it to json_object tells the model to return valid JSON, but gives you no control over the schema. Setting it to json_schema goes further: you provide a JSON Schema document and the model guarantees its output will conform to it. OpenAI calls this structured output.\nStructured output matters whenever downstream code needs to parse the model\u0026rsquo;s response. Without it, you are parsing free text — which works until the model changes a field name or adds a sentence before the JSON block. With it, you get a contract. The model\u0026rsquo;s output either matches the schema or the call fails with an error you can handle, rather than silently producing malformed data.\nA good production case for this is extracting company details from a website. I have used exactly this approach: given a company URL, extract name, description, and address as structured data, regardless of the language or layout of the page.\npackage main import ( // ... \u0026#34;github.com/openai/openai-go\u0026#34; \u0026#34;github.com/openai/openai-go/option\u0026#34; ) var client openai.Client func extractWebsiteContent(websiteURL string) (string, error) { // ... } func main() { apiKey := os.Getenv(\u0026#34;OPENAI_API_KEY\u0026#34;) if apiKey == \u0026#34;\u0026#34; { fmt.Fprintln(os.Stderr, \u0026#34;error: OPENAI_API_KEY environment variable is not set\u0026#34;) os.Exit(1) } client = openai.NewClient(option.WithAPIKey(apiKey)) scanner := bufio.NewScanner(os.Stdin) fmt.Println(\u0026#34;Please provide the company\u0026#39;s website URL:\u0026#34;) fmt.Println() if !scanner.Scan() { return } website := strings.TrimSpace(scanner.Text()) if website == \u0026#34;\u0026#34; { return } companyInformation, err := extractWebsiteContent(website) if err != nil { fmt.Fprintf(os.Stderr, \u0026#34;error: %v\\n\u0026#34;, err) return } // ... } The structure is familiar from the previous article. We initialize the client, read a URL from standard input, and pass it to extractWebsiteContent. That function is the first meaningful piece — it fetches the page and converts it into a form the model can work with efficiently.\nimport ( // ... \u0026#34;github.com/k3a/html2text\u0026#34; ) func extractWebsiteContent(websiteURL string) (string, error) { req, err := http.NewRequest(\u0026#34;GET\u0026#34;, websiteURL, nil) if err != nil { return \u0026#34;\u0026#34;, err } httpClient := \u0026amp;http.Client{} response, err := httpClient.Do(req) if err != nil { return \u0026#34;\u0026#34;, err } defer response.Body.Close() data, err := io.ReadAll(response.Body) if err != nil { return \u0026#34;\u0026#34;, err } return html2text.HTML2Text(string(data)), nil } We fetch the page using Go\u0026rsquo;s standard http package and convert the HTML body to plain text using github.com/k3a/html2text. That conversion step is more important than it looks. Raw HTML sent to the model is full of tags, scripts, and attributes that add tokens without adding meaning. Stripping them down to plain text significantly reduces the size of the input, which lowers cost and reduces the chance of the model getting distracted by irrelevant markup. Cleaner input tends to produce more accurate output.\nWith the content in hand, we define the schema we want the model to conform to.\nimport ( // ... \u0026#34;github.com/openai/openai-go/shared\u0026#34; ) var jsonSchema = shared.ResponseFormatJSONSchemaJSONSchemaParam{ Name: \u0026#34;CompanyInformation\u0026#34;, Strict: openai.Bool(true), Schema: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: map[string]interface{}{ \u0026#34;company\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The company information.\u0026#34;, \u0026#34;properties\u0026#34;: map[string]interface{}{ \u0026#34;name\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The name of the company.\u0026#34;, }, \u0026#34;description\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The description of the company.\u0026#34;, }, \u0026#34;address\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The address of the company.\u0026#34;, \u0026#34;properties\u0026#34;: map[string]interface{}{ \u0026#34;streetName\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: []string{\u0026#34;string\u0026#34;, \u0026#34;null\u0026#34;}, \u0026#34;description\u0026#34;: \u0026#34;The street name of the company.\u0026#34;, }, \u0026#34;streetNumber\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: []string{\u0026#34;string\u0026#34;, \u0026#34;null\u0026#34;}, \u0026#34;description\u0026#34;: \u0026#34;The street number of the company.\u0026#34;, }, \u0026#34;city\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: []string{\u0026#34;string\u0026#34;, \u0026#34;null\u0026#34;}, \u0026#34;description\u0026#34;: \u0026#34;The city of the company.\u0026#34;, }, }, \u0026#34;required\u0026#34;: []string{ \u0026#34;streetName\u0026#34;, \u0026#34;streetNumber\u0026#34;, \u0026#34;city\u0026#34;, }, \u0026#34;additionalProperties\u0026#34;: false, }, }, \u0026#34;required\u0026#34;: []string{ \u0026#34;name\u0026#34;, \u0026#34;description\u0026#34;, \u0026#34;address\u0026#34;, }, \u0026#34;additionalProperties\u0026#34;: false, }, }, \u0026#34;required\u0026#34;: []string{ \u0026#34;company\u0026#34;, }, \u0026#34;additionalProperties\u0026#34;: false, }, } This is a standard JSON Schema document expressed as a Go map. The Strict: true flag tells the model to follow the schema exactly. Address fields like streetName and city use a union type of string or null because not every company website publishes a full postal address (and API asks to set all fields as required). The additionalProperties: false constraint at each object level prevents the model from adding fields outside the schema.\nfunc extractCompanyInformation(companyInformation string) (string, error) { resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: openai.ChatModelGPT4_1Mini, Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage( \u0026#34;You are a legal advisor. \u0026#34; + \u0026#34;Given a company\u0026#39;s information, extract the company\u0026#39;s name, description, and address.\u0026#34;, \u0026#34;You must provide company description in English\u0026#34;, ), openai.UserMessage(companyInformation), }, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONSchema: \u0026amp;shared.ResponseFormatJSONSchemaParam{ Type: \u0026#34;json_schema\u0026#34;, JSONSchema: jsonSchema, }, }, }) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } else if len(resp.Choices) != 1 { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;unexpected API response: number of choices are %d\u0026#34;, len(resp.Choices)) } return resp.Choices[0].Message.Content, nil } The ResponseFormat field is set to OfJSONSchema with the schema we defined above. The system message defines the persona — \u0026quot;You are a legal advisor.\u0026quot; — which primes the model to treat the task with the seriousness a legal context implies, rather than producing casual summaries. We also instruct the model to respond in English regardless of the source page language, because company websites can be in any language and we want consistent output. Notice the validation on resp.Choices: rather than silently taking Choices[0] and panicking on an empty slice, we return an explicit error if the response is not what we expect.\nfunc main() { // ... companyInformation, err := extractWebsiteContent(website) if err != nil { fmt.Fprintf(os.Stderr, \u0026#34;error: %v\\n\u0026#34;, err) return } result, err := extractCompanyInformation(companyInformation) if err != nil { fmt.Fprintf(os.Stderr, \u0026#34;error: %v\\n\u0026#34;, err) return } fmt.Println(\u0026#34;Result: \u0026#34; + result) } The main function sequences the two calls: fetch and convert the page, then extract the structured data. Each step returns an error that terminates the process cleanly. The result is printed directly — at this stage it is already valid JSON conforming to our schema, ready to be unmarshalled into a Go struct by any consuming code.\nPlease provide the company\u0026#39;s website URL: https://thinksurance.de/kontakt/ Result: { \u0026#34;company\u0026#34;: { \u0026#34;address\u0026#34;: { \u0026#34;city\u0026#34;: \u0026#34;Frankfurt am Main\u0026#34;, \u0026#34;streetName\u0026#34;: \u0026#34;Niddastraße\u0026#34;, \u0026#34;streetNumber\u0026#34;: \u0026#34;91\u0026#34; }, \u0026#34;description\u0026#34;: \u0026#34;Thinksurance GmbH is a company offering...\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Thinksurance GmbH\u0026#34; } } Process finished with the exit code 0 The output is a clean JSON object with the company information extracted from a German-language page — delivered in English, as instructed. The model correctly identified that website is in German, followed the language instruction in the system message, and returned the address in the exact structure the schema required.\nWhat is reasoning_effort? # The reasoning_effort parameter controls how much internal reasoning the model does before generating its response. It applies to models that support extended thinking —the newer gpt-5 family. The options are minimal, low, medium, and high. Higher effort means the model thinks longer, produces more thorough analysis, and handles complex or ambiguous tasks better. Lower effort is faster and cheaper. For straightforward extraction tasks with a well-defined schema, high reasoning effort is wasted compute.\nThis trade-off becomes concrete when you add timing to the same extraction request we built above.\nfunc extractCompanyInformation(companyInformation string) (string, error) { start := time.Now() resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: openai.ChatModelGPT4_1Mini, Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage( \u0026#34;You are a legal advisor. \u0026#34; + \u0026#34;Given a company\u0026#39;s information, extract the company\u0026#39;s name, description, and address.\u0026#34; + \u0026#34;You must provide company description in English\u0026#34;, ), openai.UserMessage(companyInformation), }, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONSchema: \u0026amp;shared.ResponseFormatJSONSchemaParam{ Type: \u0026#34;json_schema\u0026#34;, JSONSchema: jsonSchema, }, }, }) duration := time.Since(start) // ... } Adding time.Now() before the call and time.Since(start) after gives us the wall-clock duration of the API round-trip. With gpt-4.1-mini, the baseline looks like this:\nBaseline Duration\nPlease provide the company\u0026#39;s website URL: https://thinksurance.de/kontakt/ Duration: 1.502282375s Result: { \u0026#34;company\u0026#34;: { \u0026#34;address\u0026#34;: { \u0026#34;city\u0026#34;: \u0026#34;Frankfurt am Main\u0026#34;, \u0026#34;streetName\u0026#34;: \u0026#34;Niddastraße\u0026#34;, \u0026#34;streetNumber\u0026#34;: \u0026#34;91\u0026#34; }, \u0026#34;description\u0026#34;: \u0026#34;Thinksurance GmbH is a company offering...\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Thinksurance GmbH\u0026#34; } } Process finished with the exit code 0 Around 1.5 seconds for a simple extraction — reasonable. Now let\u0026rsquo;s switch to gpt-5-nano, which does not yet have its own constant in the openai-go SDK and must be specified as a raw string.\nfunc extractCompanyInformation(companyInformation string) (string, error) { start := time.Now() resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: \u0026#34;gpt-5-nano\u0026#34;, Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage( \u0026#34;You are a legal advisor. \u0026#34; + \u0026#34;Given a company\u0026#39;s information, extract the company\u0026#39;s name, description, and address.\u0026#34; + \u0026#34;You must provide company description in English\u0026#34;, ), openai.UserMessage(companyInformation), }, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONSchema: \u0026amp;shared.ResponseFormatJSONSchemaParam{ Type: \u0026#34;json_schema\u0026#34;, JSONSchema: jsonSchema, }, }, }) duration := time.Since(start) // ... } The only change is the model name. Same task, same schema, same system prompt. The result is surprising.\nPlease provide the company\u0026#39;s website URL: https://thinksurance.de/kontakt/ Duration: 14.115678208s ... Fourteen seconds. The gpt-5 family has reasoning enabled by default — gpt-5-nano and gpt-5-mini both default to medium reasoning effort. For a task as clear-cut as extracting a company name and address from a schema, the model is doing far more internal work than the task requires. The output quality is no better; the time cost is ten times higher.\nThe fix is one line: set ReasoningEffort to minimal.\nfunc extractCompanyInformation(companyInformation string) (string, error) { start := time.Now() resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: \u0026#34;gpt-5-nano\u0026#34;, Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage( \u0026#34;You are a legal advisor. \u0026#34; + \u0026#34;Given a company\u0026#39;s information, extract the company\u0026#39;s name, description, and address.\u0026#34; + \u0026#34;You must provide company description in English\u0026#34;, ), openai.UserMessage(companyInformation), }, ReasoningEffort: \u0026#34;minimal\u0026#34;, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONSchema: \u0026amp;shared.ResponseFormatJSONSchemaParam{ Type: \u0026#34;json_schema\u0026#34;, JSONSchema: jsonSchema, }, }, }) duration := time.Since(start) // ... } Here ReasoningEffort is set to \u0026quot;minimal\u0026quot; and tells the model to skip extended internal reasoning entirely for this request.\nPlease provide the company\u0026#39;s website URL: https://thinksurance.de/kontakt/ Duration: 1.349047041s Result: { \u0026#34;company\u0026#34;: { \u0026#34;address\u0026#34;: { \u0026#34;city\u0026#34;: \u0026#34;Frankfurt am Main\u0026#34;, \u0026#34;streetName\u0026#34;: \u0026#34;Niddastraße\u0026#34;, \u0026#34;streetNumber\u0026#34;: \u0026#34;91\u0026#34; }, \u0026#34;description\u0026#34;: \u0026#34;Thinksurance GmbH is a company offering...\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Thinksurance GmbH\u0026#34; } } Process finished with the exit code 0 Back to 1.3 seconds, with identical output quality. To be clear, the reasoning is not bad — it is that reasoning should match the task. For complex analysis, multi-step problem solving, or ambiguous requirements, higher reasoning_effort earns its cost. For deterministic extraction with a well-defined schema, minimal is the right setting. Matching the effort level to the task is one of the more impactful tuning decisions you can make when running LLMs in production at volume.\nConclusion # The Chat Completions API gives you more control than most developers use. response_format with a JSON Schema turns the model into a reliable data extraction tool — no parsing hacks, no fragile string matching, just structured output you can trust. reasoning_effort lets you tune the cost-latency trade-off for models that reason by default, and the difference between the wrong and right setting can be an order of magnitude. Neither parameter requires complex code changes; both have an immediate effect on what the model produces and how quickly it produces it.\nUseful Resources # Chat Completions API reference OpenAI structured outputs guide JSON Schema specification openai-go SDK html2text package ","date":"6 March 2026","externalUrl":null,"permalink":"/article/golang/llm-and-golang-gpt-api-parameters/","section":"Articles","summary":"In the previous article I covered the fundamentals of the Chat Completions API: setting up a client, maintaining conversation history, and integrating tools. That was enough to build a working conversational agent. This article goes a level deeper — into the API parameters that shape what the model returns and how it thinks.\nTwo parameters stand out as particularly useful in production: response_format and reasoning_effort. The first gives you control over the structure of the model’s output. The second controls how much the model reasons before responding — which turns out to matter more than you might expect once you start caring about latency and cost.\nChat Completions API details # The Chat Completions API endpoint accepts a rich set of parameters. Most have sensible defaults and you will rarely touch them, but understanding what is available saves you from reaching for workarounds that already exist in the API. The table below covers the current non-deprecated parameters from the API reference:\nParameter Type Description model string ID of the model to use messages array Conversation history as an ordered list of messages response_format object Output format: text, json_object, or json_schema reasoning_effort string Reasoning intensity for reasoning models: low, medium, high temperature number Sampling temperature from 0 to 2; higher values produce more random output top_p number Alternative to temperature; nucleus sampling probability mass max_completion_tokens integer Maximum tokens the model may generate in the response n integer Number of completion choices to return stream boolean Stream partial responses as server-sent events stop string/array Sequences at which the API stops generating presence_penalty number Penalises new tokens based on whether they appear in the text so far frequency_penalty number Penalises new tokens based on their frequency in the text so far tools array List of tools (functions) the model may call tool_choice string/object Controls which tool the model calls seed integer Seed for deterministic sampling user string Unique identifier for the end user In this article we focus on response_format and reasoning_effort — two parameters with a direct, visible impact on production systems.\nInformation extraction with response_format # The response_format parameter controls how the model structures its output. The default is plain text. Setting it to json_object tells the model to return valid JSON, but gives you no control over the schema. Setting it to json_schema goes further: you provide a JSON Schema document and the model guarantees its output will conform to it. OpenAI calls this structured output.\n","title":"LLM and Go: Investigating OpenAI Chat Completions API","type":"article"},{"content":"For most of my career, integrating external intelligence into an application meant calling a rules engine, training a custom classifier, or encoding business logic that someone had painfully documented in a spreadsheet. The idea that I could describe a task in plain language and have a model respond with genuine reasoning was not something I expected to become production-ready in my working life. Then GPT happened, and it changed what backend developers need to know.\nThis article is the first in a series on using LLMs in Go. We start with the OpenAI Chat Completions API — the stateless, request-based interface that gives you direct control over every aspect of the conversation. By the end, you will have a working conversational agent that can call external tools to answer questions it otherwise could not.\nA short introduction to ChatGPT and OpenAI # The path to large language models runs through a decade of incremental progress in deep learning. Early models like word2vec and GloVe learned to embed words into dense vector spaces, capturing semantic relationships between terms. The transformer architecture, introduced by Google in 2017, changed the trajectory of the field — it processes sequences in parallel using attention mechanisms that capture long-range dependencies far more effectively than recurrent networks. This architectural shift made it practical to train models on orders of magnitude more data. GPT-1 in 2018 showed that large-scale unsupervised pre-training followed by fine-tuning could match or beat purpose-built models across a range of language tasks.\nUnderstanding what these models actually do removes a lot of the mysticism around them. An LLM is, at its core, a next-token predictor. It takes a sequence of tokens as input and outputs a probability distribution over the vocabulary for the next token. The transformer\u0026rsquo;s attention mechanism allows every token in the input to attend to every other token, building a rich contextual representation before making that prediction. Training adjusts billions of parameters to minimise prediction error across enormous text corpora. What emerges is a model with broad world knowledge encoded in its weights — not because it was taught facts directly, but because predicting text well requires internalising the structure of the world that produced that text.\nChatGPT is OpenAI\u0026rsquo;s conversational product built on the GPT model series. What set it apart from raw GPT-3 was the addition of reinforcement learning from human feedback (RLHF) — a technique that fine-tunes the base model to follow instructions and produce responses that human raters judge as helpful and safe. When ChatGPT launched in late 2022, it became one of the fastest-adopted consumer products in history. For developers, the more relevant artefact is the API behind it — specifically the Chat Completions API, which gives programmatic access to the same models powering the product.\nChat Completions API vs Responses API # OpenAI exposes its models through two primary APIs. The Chat Completions API is the older and more fundamental of the two. It is stateless: you send a list of messages representing the full conversation history, and the model returns the next message. Every request is self-contained. The application is entirely responsible for maintaining that history and re-sending it with every call.\nThe Responses API, introduced in 2025, takes a different approach. It manages conversation state on the server side, supports built-in tools such as web search and file reading, and is designed for agent-oriented use cases where you want OpenAI\u0026rsquo;s infrastructure to handle more of the orchestration. The trade-off is less control over what is sent to the model and tighter coupling to OpenAI\u0026rsquo;s platform.\nFeature Chat Completions API Responses API Conversation state Client-managed Server-managed History management Manual — sent with every request Automatic Tool support Manual function calling Built-in tools (web search, code interpreter) Streaming Yes Yes Control Full Limited Vendor coupling Low Higher Best for Custom agents, full control Rapid prototyping, built-in tooling In this series we use the Chat Completions API. It requires more wiring on your side, but it gives you a clearer mental model of what is actually happening — which matters when things go wrong in production.\nFirst AI agent # The official Go SDK for the OpenAI API is openai-go, maintained by OpenAI directly. It provides typed bindings for all major API endpoints and handles authentication, serialisation, and retries. It covers Chat Completions, Responses, embeddings, images, and more. For this series, we use it exclusively — no third-party wrappers.\nTo call the API, you need a secret key from OpenAI. Create an account at platform.openai.com, navigate to the API Keys section, and generate a new key. Store it in an environment variable and never commit it to version control.\npackage main import ( // ... \u0026#34;github.com/openai/openai-go\u0026#34; \u0026#34;github.com/openai/openai-go/option\u0026#34; ) var history []openai.ChatCompletionMessageParamUnion var client openai.Client func main() { apiKey := os.Getenv(\u0026#34;OPENAI_API_KEY\u0026#34;) if apiKey == \u0026#34;\u0026#34; { fmt.Fprintln(os.Stderr, \u0026#34;error: OPENAI_API_KEY environment variable is not set\u0026#34;) os.Exit(1) } client = openai.NewClient(option.WithAPIKey(apiKey)) history = append(history, openai.SystemMessage( \u0026#34;You are a helpful assistant.\u0026#34;, )) } Two things are initialised here: the API client and the conversation history. The client is created with the API key read from the environment — if the variable is missing, the application exits immediately rather than failing later with a cryptic authentication error. The history slice is the conversation state. Unlike the Responses API, the Chat Completions API has no memory of its own. Every request must include the full conversation history so the model has the context it needs to respond correctly. This slice is where we maintain it. The System Message at the top of the history defines the model\u0026rsquo;s behaviour and persona for the entire session. It is always the first message and shapes how the model interprets everything that follows.\nimport ( \u0026#34;bufio\u0026#34; // ... ) func talkToAgent() (string, error) { return \u0026#34;\u0026#34;, nil } func main() { // ... scanner := bufio.NewScanner(os.Stdin) fmt.Println(\u0026#34;AI assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit.\u0026#34;) fmt.Println() for { fmt.Print(\u0026#34;Human: \u0026#34;) if !scanner.Scan() { break } input := strings.TrimSpace(scanner.Text()) if input == \u0026#34;\u0026#34; { continue } if strings.EqualFold(input, \u0026#34;exit\u0026#34;) { fmt.Println(\u0026#34;Bye.\u0026#34;) break } history = append(history, openai.UserMessage(input)) answer, err := talkToAgent() if err != nil { fmt.Fprintf(os.Stderr, \u0026#34;error: %v\\n\u0026#34;, err) continue } fmt.Printf(\u0026#34;Agent: %s\\n\\n\u0026#34;, answer) } } The main loop reads from standard input using bufio.Scanner, which handles line-by-line reading cleanly. Empty inputs are skipped. Typing exit terminates the application. For any other input, the user\u0026rsquo;s message is appended to the history as a UserMessage and passed to talkToAgent. The result is printed back to the terminal. At this stage, talkToAgent is a stub that returns nothing — we fill it in next.\nfunc talkToAgent() (string, error) { resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: openai.ChatModelGPT4_1Mini, Messages: history, }) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } choice := resp.Choices[0] history = append(history, choice.Message.ToParam()) return choice.Message.Content, nil } Here Chat.Completions.New sends the full history to the model and returns a completion response. The Model field specifies which model to use — here we use gpt-4.1-mini to keep costs low during development. The response contains a Choices slice; we take the first choice, which is the model\u0026rsquo;s response. The important step is calling ToParam() on the response message before appending it to the history. This converts the assistant\u0026rsquo;s response into the format expected for the chat history, so the model will remember what it said in subsequent turns.\nThe three GPT-4.1 models cover different points on the cost-quality spectrum:\nModel Input cost (per 1M tokens) Output cost (per 1M tokens) Best for gpt-4.1 $2.00 $8.00 Complex reasoning, production quality gpt-4.1-mini $0.40 $1.60 Balanced cost and quality gpt-4.1-nano $0.10 $0.40 High-volume, low-complexity tasks For development and experimentation, gpt-4.1-mini or gpt-4.1-nano are the right starting point. The quality is sufficient for most tasks, and the cost difference against gpt-4.1 is significant at scale.\nAI assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit. Human: hi Agent: Hello! How can I assist you today? Human: what is my name? Agent: I am sorry, I do not know your name. Feel free to tell me! Human: my name is Marko Agent: Nice to meet you, Marko! How can I help you today? Human: what is my name? Agent: Your name is Marko! How can I assist you further? The key thing to notice in this exchange is that the agent remembers the user\u0026rsquo;s name. That is not magic — it is the history slice doing its job. Every time a user sends a message and the agent replies, both messages are appended to the history. On the next request, the full history is sent to the model, giving it complete context. Without that history, the model would have no idea who Marko (me!) is.\nFirst tool integration # Even with full conversation history, the model has a fundamental limitation: it cannot access real-time information. Ask it for the current time, and it will tell you so.\nHuman: what is the current time in Frankfurt? Agent: I can\u0026#39;t provide real-time information, including the current time. However, Frankfurt is in the Central European Time Zone (CET), which is UTC+1, and it observes Central European Summer Time (CEST), which is UTC+2 during the summer months. You can easily check the current time using a clock or a smartphone. Is there anything else you would like to know? This is where tools come in. A tool is a function that the model can request to be called on its behalf. You define the tool — its name, description, and parameter schema — and send those definitions alongside the conversation history. When the model determines it needs information it cannot generate from its weights alone, it responds not with text but with a list of tool calls it wants executed. Your application runs those calls, sends the results back, and the model incorporates them into its final response. The model never executes code directly; it only requests calls and receives results.\nThe integration process has a clear shape: send the tool definitions with every request, check whether the response contains tool calls, execute each call, append the results to the history as tool messages, and send another request. Repeat until the model returns a plain assistant message with no tool calls. This loop is what makes the agent able to use multiple tools in sequence when needed.\nThe first step is building the function the tool will call.\nfunc getCurrentTime(timezone string) (string, error) { location, err := time.LoadLocation(timezone) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to load location: %w\u0026#34;, err) } now := time.Now().In(location) return now.Format(\u0026#34;2006-01-02 15:04:05\u0026#34;), nil } This is a plain Go function with no awareness of the LLM. It takes a timezone variable, loads the location, and returns the current time formatted as a string. Keeping tool implementations as ordinary functions is intentional — it keeps them testable and reusable outside the agent context.\nWith the function in place, we need to describe it to the model and update the request loop.\nvar getTimeToolDefinition = openai.ChatCompletionToolParam{ Type: \u0026#34;function\u0026#34;, Function: openai.FunctionDefinitionParam{ Name: \u0026#34;get_time\u0026#34;, Description: openai.String(\u0026#34;Fetch the current time in a given timezone.\u0026#34;), Parameters: openai.FunctionParameters{ \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: map[string]interface{}{ \u0026#34;timezone\u0026#34;: map[string]interface{}{ \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Tne international timezone name, e.g. America/Los_Angeles.\u0026#34;, }, }, \u0026#34;required\u0026#34;: []string{\u0026#34;timezone\u0026#34;}, \u0026#34;additionalProperties\u0026#34;: false, }, }, } func talkToAgent() (string, error) { for { resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: openai.ChatModelGPT4_1Mini, Messages: history, Tools: []openai.ChatCompletionToolParam{ getTimeToolDefinition, }, }) // ... } } The tool definition is a struct that describes the function to the model. The Name field is what the model uses when it requests a call. The Description is critical — it is what the model reads to decide whether to use this tool at all, so it should be precise. The Parameters block follows the JSON Schema format and tells the model exactly what arguments to provide.\nThe request loop in talkToAgent is now wrapped in a for loop because a single user message may require multiple round-trips: the model calls a tool, you return the result, and the model may call another tool before finally producing its answer.\nfunc callTool(call openai.ChatCompletionMessageToolCall) (string, error) { // ... } func talkToAgent() (string, error) { for { resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: openai.ChatModelGPT4_1Mini, Messages: history, Tools: []openai.ChatCompletionToolParam{ getTimeToolDefinition, }, }) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;API call failed: %w\u0026#34;, err) } choice := resp.Choices[0] history = append(history, choice.Message.ToParam()) if len(choice.Message.ToolCalls) == 0 { return choice.Message.Content, nil } for _, call := range choice.Message.ToolCalls { fmt.Printf(\u0026#34; [tool] %s(%s)\\n\u0026#34;, call.Function.Name, call.Function.Arguments) result, err := callTool(call) if err != nil { return \u0026#34;\u0026#34;, err } history = append(history, openai.ToolMessage(result, call.ID)) } } } When the model returns a response with no tool calls, ToolCalls is empty and we return the content directly to the caller. When it does contain tool calls, we iterate over each one, print it for visibility, execute it via callTool, and append the result to the history as a ToolMessage tied to the call\u0026rsquo;s ID. The model uses that ID to match results back to the requests it made. The loop then fires another request with the updated history, and the cycle continues until the model is satisfied.\nfunc callTool(call openai.ChatCompletionMessageToolCall) (string, error) { switch call.Function.Name { case \u0026#34;get_time\u0026#34;: var args timeArgs if err := json.Unmarshal([]byte(call.Function.Arguments), \u0026amp;args); err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to parse tool arguments: %w\u0026#34;, err) } currentTime, err := getCurrentTime(args.Timezone) if err != nil { errMsg, _ := json.Marshal(map[string]string{\u0026#34;error\u0026#34;: err.Error()}) return string(errMsg), nil } result := timeResult{ Time: currentTime, } out, err := json.Marshal(result) if err != nil { return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;failed to marshal tool result: %w\u0026#34;, err) } return string(out), nil default: return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;unknown tool: %q\u0026#34;, call.Function.Name) } } The function callTool is a router. It receives the tool call struct from the model, switches on the function name, and dispatches to the appropriate implementation. For get_time, it unmarshals the JSON arguments the model provided, calls getCurrentTime, and serialises the result back to JSON. If the tool execution fails — for example, an invalid timezone — the error is returned as a JSON payload rather than bubbling up to the caller. This keeps the loop running: the model receives the error, understands what went wrong, and can either try a corrected call or inform the user gracefully. Any tool name the application does not recognise returns an error, which surfaces cleanly through the loop.\nThe last piece is updating the system prompt to give the model clear instructions about when to use the tool.\nfunc main() { // ... client = openai.NewClient(option.WithAPIKey(apiKey)) history = append(history, openai.SystemMessage( \u0026#34;You are a helpful time assistant. \u0026#34;+ \u0026#34;When asked about time at particular locations, \u0026#34;+ \u0026#34;always use the get_time tool to get current time in a given timezone. \u0026#34;+ \u0026#34;Never guess the time.\u0026#34;, )) // ... The system prompt now explicitly instructs the model to use the get_time tool whenever time at a specific location is requested. Without this instruction, the model might attempt to reason from its training data and produce a plausible but wrong answer. The instruction removes the ambiguity, especially by saying that the model should never guess the time itself.\nTime assistant ready. Type your question or \u0026#39;exit\u0026#39; to quit. You: hi Agent: Hello! How can I assist you today? You: what is the current time in Frankfurt? [tool] get_time({\u0026#34;timezone\u0026#34;:\u0026#34;Europe/Berlin\u0026#34;}) Agent: The current time in Frankfurt is 14:23 on March 5, 2026. The [tool] line shows the model calling get_time with the correct IANA timezone for Frankfurt. The model resolved \u0026ldquo;Frankfurt\u0026rdquo; to Europe/Berlin on its own, based on the tool description and its world knowledge. The full source for this example is available at llm-and-golang-examples.\nConclusion # The Chat Completions API is the right starting point for anyone building LLM-powered features in Go. It is explicit, stateless, and gives you full control over what the model sees. The history management is your responsibility, but that also means you understand exactly what is happening in every request. Adding tools extends the model\u0026rsquo;s reach into live data without complicating the core loop much — define the tool, check for calls, execute, return results, repeat. This pattern scales to multiple tools cleanly. The next article in this series will look at the Responses API and where it makes more sense than the Chat Completions approach.\nUseful Resources # openai-go SDK Chat Completions API reference OpenAI model overview Full source code ","date":"5 March 2026","externalUrl":null,"permalink":"/article/golang/llm-and-golang-gpt-chat-completion/","section":"Articles","summary":"For most of my career, integrating external intelligence into an application meant calling a rules engine, training a custom classifier, or encoding business logic that someone had painfully documented in a spreadsheet. The idea that I could describe a task in plain language and have a model respond with genuine reasoning was not something I expected to become production-ready in my working life. Then GPT happened, and it changed what backend developers need to know.\nThis article is the first in a series on using LLMs in Go. We start with the OpenAI Chat Completions API — the stateless, request-based interface that gives you direct control over every aspect of the conversation. By the end, you will have a working conversational agent that can call external tools to answer questions it otherwise could not.\nA short introduction to ChatGPT and OpenAI # The path to large language models runs through a decade of incremental progress in deep learning. Early models like word2vec and GloVe learned to embed words into dense vector spaces, capturing semantic relationships between terms. The transformer architecture, introduced by Google in 2017, changed the trajectory of the field — it processes sequences in parallel using attention mechanisms that capture long-range dependencies far more effectively than recurrent networks. This architectural shift made it practical to train models on orders of magnitude more data. GPT-1 in 2018 showed that large-scale unsupervised pre-training followed by fine-tuning could match or beat purpose-built models across a range of language tasks.\nUnderstanding what these models actually do removes a lot of the mysticism around them. An LLM is, at its core, a next-token predictor. It takes a sequence of tokens as input and outputs a probability distribution over the vocabulary for the next token. The transformer’s attention mechanism allows every token in the input to attend to every other token, building a rich contextual representation before making that prediction. Training adjusts billions of parameters to minimise prediction error across enormous text corpora. What emerges is a model with broad world knowledge encoded in its weights — not because it was taught facts directly, but because predicting text well requires internalising the structure of the world that produced that text.\nChatGPT is OpenAI’s conversational product built on the GPT model series. What set it apart from raw GPT-3 was the addition of reinforcement learning from human feedback (RLHF) — a technique that fine-tunes the base model to follow instructions and produce responses that human raters judge as helpful and safe. When ChatGPT launched in late 2022, it became one of the fastest-adopted consumer products in history. For developers, the more relevant artefact is the API behind it — specifically the Chat Completions API, which gives programmatic access to the same models powering the product.\n","title":"LLM and Go: OpenAI Integration via Chat Completions API","type":"article"},{"content":"Go 1.22 introduced range over functions, and Go 1.23 brought the iter package to go with it. Together they gave iterators a proper place in the language. Before that, iterating over custom data structures meant either returning slices upfront — loading everything into memory — or writing callback-based helpers that nobody could agree on naming. I have seen both approaches and neither felt right.\nThe core idea behind iterators is straightforward: instead of computing all values upfront and handing them back as a list, you compute each value on demand and yield it to the caller one at a time. The caller controls when to stop. This matters any time you are working with large or potentially infinite sequences.\nThis article walks through why iterators exist in Go, how the yield-based pattern works, what the iter package provides, and where the current limits of the feature sit.\nWhy do we need Iterators? # The simplest case for iteration is a slice of numbers. You range over it, print each value, move on.\nfunc main() { numbers := []int{1, 2, 3, 4, 5} for _, i := range numbers { fmt.Println(i) } } // OUT: // 1 // 2 // 3 // 4 // 5 That works fine until the collection gets large. If you need to generate a million numbers, you have to allocate memory for all of them before you can even start ranging.\nfunc main() { n := 1_000_000 numbers := make([]int, n) for i := range numbers { numbers[i] = i * 2 } for _, i := range numbers { fmt.Println(i) } } // OUT: // 1 // 2 // ... You can always add a break once you hit your threshold, but the damage is already done — the entire slice was allocated upfront. In other cases, you might not even know how many items you will need. The for range loop can iterate for some time, until it reaches the breakpoint, depending on some value provided in the item. In such cases, the size of such a list must be not just too big, but absolutely unpredictable.\nfunc main() { n := 1_000_000 numbers := make([]int, n) for i := range numbers { numbers[i] = i * 2 } for _, i := range numbers { fmt.Println(i) if i \u0026gt; 10 { break } } } // OUT: // 1 // 2 // 4 // 8 // 10 // 12 In a real application, the decision about when to stop often happens dynamically — driven by user input, a timeout, or a condition that evaluates to true before the fifth item. Allocating a million items and then breaking on the fifth is wasteful. This is exactly the problem iterators solve.\nIterators in Go # An iterator in Go is a function that accepts a yield function as its argument. For each item in the sequence, it calls yield with that item. If yield returns false — which happens when the caller breaks out of the loop or returns from the enclosing function — the iterator should stop too.\nfunc iterateNumbers(yield func(number int) bool) { numbers := []int{10, 20, 30, 40, 50} for _, number := range numbers { if !yield(number) { // returns false on break, return or no more items break } } } func main() { for i := range iterateNumbers { fmt.Println(i) } } // OUT: // 10 // 20 // 30 // 40 // 50 The yield function returns true as long as the caller wants to continue, and false the moment they stop. Checking that return value and breaking out of the internal loop is what makes the iterator cooperate with us in the first place.\nfunc main() { for i := range iterateNumbers { fmt.Println(i) break } } // OUT: // 10 Break works exactly as expected. The iterator gets the signal on the next yield call and stops cleanly.\nfunc main() { for i := range iterateNumbers { if i%20 == 0 { continue } fmt.Println(i) } } // OUT: // 10 // 30 // 50 Here continue does not interrupt the loop — it skips the current iteration body and moves to the next value. The iterator keeps running.\nOne thing worth noting: yield is not a reserved word in Go. You can name the yield function parameter whatever you like, like in the example below.\nfunc iterateNumbers(continueIteration func(number int) bool) { numbers := []int{10, 20, 30, 40, 50} for _, number := range numbers { if !continueIteration(number) { // returns false on break, return or no more items break } } } func main() { for i := range iterateNumbers { fmt.Println(i) } } // OUT: // 10 // 20 // 30 // 40 // 50 The name yield has become the convention because it communicates intent clearly, known from other programming languages, but that is not the case in Go. In the example above, the yield function is called continueIteration, which clearly indicates that you can call it whatever you like.\nHow to fix memory allocation? # The real power of iterators shows up when you generate values lazily — computing each item only when the caller asks for it, with no upfront allocation.\nfunc iterateNumbersDynamically(yield func(number int) bool) { number := 0 for { if !yield(number) { // returns false on break, return or no more items break } number += 2 } } func main() { for i := range iterateNumbersDynamically { fmt.Println(i) if i \u0026gt; 10 { break } } } // OUT: // 0 // 2 // 4 // 6 // 8 // 10 // 12 This iterator has no internal collection at all. It computes the next value from the previous one and yields it. You can iterate over as many items as you need without allocating memory for items you will never see. As you can see, the million-number problem from earlier is gone.\nIntroduction to iter package # Go 1.23 formalized the iterator pattern by introducing the iter package and two named types.\npackage iter type Seq[K any] func(yield func(K) bool) Here Seq is a generic function type that represents a sequence of single values. Any function that matches this signature can be used directly in a for loop. It wraps the pattern we built manually in the previous sections into a named, reusable type.\nfunc iterateWithIter() iter.Seq[int] { return func(yield func(int) bool) { number := 0 for { if !yield(number) { // returns false on break, return or no more items break } number += 3 } } } func main() { for i := range iterateWithIterator() { fmt.Println(i) if i \u0026gt; 10 { break } } } // OUT: // 0 // 3 // 6 // 9 // 12 For sequences of pairs — like key-value, index-value, or value-error — there is Seq2.\npackage iter type Seq2[K, V any] func(yield func(K, V) bool) So the example with Seq2 might look like this:\nfunc iterateWithIter2() iter.Seq2[int, string] { return func(yield func(int, string) bool) { number := 0 text := fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) for { if !yield(number, text) { break } number += 3 text = fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) } } } func main() { for i, v := range iterateWithIter2() { fmt.Println(i, v) if i \u0026gt; 10 { break } } } // OUT: // 0 number: 0 // 3 number: 3 // 6 number: 6 // 9 number: 9 // 12 number: 12 A practical use of Seq2 is reading a file line by line. Instead of loading the entire file into memory, you yield one line at a time alongside any error that occurred while scanning.\ntype FileReader struct { filePath string } func (f *FileReader) ReadLine() iter.Seq2[string, error] { return func(yield func(string, error) bool) { file, err := os.Open(f.filePath) if err != nil { yield(\u0026#34;before lines\u0026#34;, err) return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() err := scanner.Err() if err != nil { yield(\u0026#34;\u0026#34;, err) return } if !yield(\u0026#34;new line: \u0026#34;+line, nil) { return } } } } func main() { reader := FileReader{filePath: \u0026#34;./file_reader/main.go\u0026#34;} for s, e := range reader.ReadLine() { fmt.Println(s, e) } } // new line: type FileReader struct { \u0026lt;nil\u0026gt; // new line: filePath string \u0026lt;nil\u0026gt; // new line: } \u0026lt;nil\u0026gt; // new line: \u0026lt;nil\u0026gt; // new line: func (f *FileReader) ReadLine() iter.Seq2[string, error] { \u0026lt;nil\u0026gt; // new line: return func(yield func(string, error) bool) { \u0026lt;nil\u0026gt; // ... The first value is the line content, the second is any error that surfaced during the scan. The caller handles both in the same loop where they consume the lines, which keeps the error handling close to the data. This example is one concrete useful application of Seq2 and iterators in general. During the execution of the application, you can\u0026rsquo;t predict how many lines there will be in the file - and there can be very many of them. The Seq2 type is a perfect fit for this, because it allows you to yield each line, and break at any point when you find the line which might match your regex pattern, for example.\nFunctions Pull and Pull2 from iter package # Types Seq and Seq2 are push-based: the iterator drives the loop by calling yield. Sometimes you want the opposite — a pull-based model where you ask for the next value explicitly. The iter package provides iter.Pull and iter.Pull2 for this.\nfunc iterateWithIter() iter.Seq[string] { return func(yield func(string) bool) { counter := 0 number := 0 text := fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) for { if !yield(text) { // returns false on break, return or no more items break } number += 3 text = fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) counter++ if counter == 5 { yield(text) return } } } } func main() { next, stop := iter.Pull(iterateWithIter()) defer stop() for { k, ok := next() if !ok { break } fmt.Println(k) } } // OUT: // number: 0 // number: 3 // number: 6 // number: 9 // number: 12 // number: 15 Here iter.Pull returns two functions: next and stop. Each call to next returns the next value in the sequence and a boolean indicating whether returned value is an actual item. When the sequence is exhausted, next returns the zero value of type V and false. Function stop stops the iterator and releases all resources associated with it.\nfunc iterateWithIter2() iter.Seq2[int, string] { return func(yield func(int, string) bool) { counter := 0 number := 0 text := fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) for { if !yield(number, text) { // returns false on break, return or no more items break } number += 3 text = fmt.Sprintf(\u0026#34;number: %d\u0026#34;, number) counter++ if counter == 5 { yield(number, text) return } } } } func main() { next2, stop2 := iter.Pull2(iterateWithIter2()) defer stop2() for { k, v, ok := next2() if !ok { break } fmt.Println(k, v) } } // OUT: // 0 number: 0 // 3 number: 3 // 6 number: 6 // 9 number: 9 // 12 number: 12 // 15 number: 15 With iter.Pull2 it is the same idea applied to Seq2. Its next function returns three values: the two from the pair and the boolean.\nCan we use iterator for yielding triplets? # The iter package stops at pairs. There is no Seq3. Sure, you can define the type yourself:\ntype Seq3[K, V, T any] func(yield func(K, V, T) bool) But you cannot use it in a for loop. The language spec for range loops only recognises functions whose yield takes one or two arguments. Three is outside that boundary.\nfunc iterateWithIterator3() Seq3[int, string, string] { return func(yield func(int, string, string) bool) { /// ... } } Honestly, this is not much of a limitation in practice. If you need to yield three or more values together, wrap them in a struct and use Seq or Seq2. A struct is a cleaner holder for multiple related values than a growing list of type parameters anyway.\nConclusion # Go\u0026rsquo;s iterator model is a small surface area with meaningful depth. The yield-based push model integrates cleanly with for loops, and iter.Pull covers the cases where you need explicit control over when the next value is fetched. The standard library types Seq and Seq2 give you named anchors to build on. The memory allocation problem that motivated this feature — allocating everything upfront just to break early — has a clean solution now.\nUseful Resources # iter package bufio package Go 1.22 release notes — range over functions Go 1.23 release notes — iter package ","date":"3 March 2026","externalUrl":null,"permalink":"/article/golang/tutorial-iterators/","section":"Articles","summary":"Go 1.22 introduced range over functions, and Go 1.23 brought the iter package to go with it. Together they gave iterators a proper place in the language. Before that, iterating over custom data structures meant either returning slices upfront — loading everything into memory — or writing callback-based helpers that nobody could agree on naming. I have seen both approaches and neither felt right.\nThe core idea behind iterators is straightforward: instead of computing all values upfront and handing them back as a list, you compute each value on demand and yield it to the caller one at a time. The caller controls when to stop. This matters any time you are working with large or potentially infinite sequences.\nThis article walks through why iterators exist in Go, how the yield-based pattern works, what the iter package provides, and where the current limits of the feature sit.\nWhy do we need Iterators? # The simplest case for iteration is a slice of numbers. You range over it, print each value, move on.\nfunc main() { numbers := []int{1, 2, 3, 4, 5} for _, i := range numbers { fmt.Println(i) } } // OUT: // 1 // 2 // 3 // 4 // 5 That works fine until the collection gets large. If you need to generate a million numbers, you have to allocate memory for all of them before you can even start ranging.\nfunc main() { n := 1_000_000 numbers := make([]int, n) for i := range numbers { numbers[i] = i * 2 } for _, i := range numbers { fmt.Println(i) } } // OUT: // 1 // 2 // ... You can always add a break once you hit your threshold, but the damage is already done — the entire slice was allocated upfront. In other cases, you might not even know how many items you will need. The for range loop can iterate for some time, until it reaches the breakpoint, depending on some value provided in the item. In such cases, the size of such a list must be not just too big, but absolutely unpredictable.\nfunc main() { n := 1_000_000 numbers := make([]int, n) for i := range numbers { numbers[i] = i * 2 } for _, i := range numbers { fmt.Println(i) if i \u003e 10 { break } } } // OUT: // 1 // 2 // 4 // 8 // 10 // 12 In a real application, the decision about when to stop often happens dynamically — driven by user input, a timeout, or a condition that evaluates to true before the fifth item. Allocating a million items and then breaking on the fifth is wasteful. This is exactly the problem iterators solve.\n","title":"Go Tutorial: Iterators","type":"article"},{"content":"","date":"3 March 2026","externalUrl":null,"permalink":"/tags/iterators/","section":"Tags","summary":"","title":"Iterators","type":"tags"},{"content":"","date":"3 March 2026","externalUrl":null,"permalink":"/series/iterators-in-go/","section":"Series","summary":"","title":"Iterators in Go","type":"series"},{"content":" Hi, I\u0026rsquo;m Marko # I\u0026rsquo;m a Software Architect originally from Serbia, now living in Germany. I have over 20 years of experience in backend engineering, and I currently work as a System Architect at Thinksurance — a German insurtech platform — where I work across Go microservices, workflow automation with n8n, and production AI systems built on pgvector, Supabase, Azure embeddings, and GPT.\nBeyond my day job, I try to give back to the developer community through writing and open source. I previously published on Medium, where my articles on Go reached a few thousand readers. This blog is the next step — a place I own, on topics I care about, written from production experience.\nYou can also find some of my code on GitHub.\nWhat this blog is about # I write about three areas where I have hands-on production experience:\nGo — architecture patterns, standard library deep-dives, testing strategies, real-world service design LLMs and RAG — practical implementations using pgvector, Supabase, Azure embeddings, and GPT Workflow automation — n8n workflows integrated with backend systems The goal is to share what I\u0026rsquo;ve learned building real systems — useful for backend developers navigating the same territory.\nConsulting # I occasionally take on consulting work in areas where I have direct production experience:\nBackend architecture (Go, microservices, AWS) LLM and RAG integration into existing systems Workflow automation with n8n If you have a specific problem that fits, feel free to reach out via LinkedIn or email me at marko.milojevic@ompluscator.io.\nOutside of work # Awful chess player. Gym regular. Learning harmonica. Cat owner — the cat is not impressed.\nSubscribe to the newsletter below if you want new articles and curated links delivered to your inbox.\n","date":"1 January 2026","externalUrl":null,"permalink":"/general/about/","section":"Generals","summary":"Hi, I’m Marko # I’m a Software Architect originally from Serbia, now living in Germany. I have over 20 years of experience in backend engineering, and I currently work as a System Architect at Thinksurance — a German insurtech platform — where I work across Go microservices, workflow automation with n8n, and production AI systems built on pgvector, Supabase, Azure embeddings, and GPT.\nBeyond my day job, I try to give back to the developer community through writing and open source. I previously published on Medium, where my articles on Go reached a few thousand readers. This blog is the next step — a place I own, on topics I care about, written from production experience.\nYou can also find some of my code on GitHub.\nWhat this blog is about # I write about three areas where I have hands-on production experience:\nGo — architecture patterns, standard library deep-dives, testing strategies, real-world service design LLMs and RAG — practical implementations using pgvector, Supabase, Azure embeddings, and GPT Workflow automation — n8n workflows integrated with backend systems The goal is to share what I’ve learned building real systems — useful for backend developers navigating the same territory.\nConsulting # I occasionally take on consulting work in areas where I have direct production experience:\nBackend architecture (Go, microservices, AWS) LLM and RAG integration into existing systems Workflow automation with n8n If you have a specific problem that fits, feel free to reach out via LinkedIn or email me at marko.milojevic@ompluscator.io.\nOutside of work # Awful chess player. Gym regular. Learning harmonica. Cat owner — the cat is not impressed.\nSubscribe to the newsletter below if you want new articles and curated links delivered to your inbox.\n","title":"About","type":"general"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/general/","section":"Generals","summary":"","title":"Generals","type":"general"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/tags/compare/","section":"Tags","summary":"","title":"Compare","type":"tags"},{"content":"With the release of Go 1.22, the Go standard library introduced several new features. As you might have noticed in articles related to the previous release, here we mostly concentrate on the new exciting packages and features that they give us. This article will start this journey, by providing a deeper look into the implementation of the version package in Go.\nLang # The first function we are ready to examine is the Lang function. This function provides a cleaned, valid Go version as a string. In case it can\u0026rsquo;t determine the actual version, due to an invalid state of the string value, it will return an string as a result.\nLang function\nfunc Lang(x string) string As we can see the function signature above, function expects one argument, a string, that represents a Go version. An output should be also one value, a string, as a cleaned Go version.\nLang function examples\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;go/version\u0026#34; ) func main() { fmt.Println(version.Lang(\u0026#34;go1.0\u0026#34;)) // go1 fmt.Println(version.Lang(\u0026#34;go1\u0026#34;)) // go1 fmt.Println(version.Lang(\u0026#34;go1.22.4\u0026#34;)) // go1.22 fmt.Println(version.Lang(\u0026#34;go1.22.3\u0026#34;)) // go1.22 fmt.Println(version.Lang(\u0026#34;go1.22.2\u0026#34;)) // go1.22 fmt.Println(version.Lang(\u0026#34;go1.22.rc1\u0026#34;)) // fmt.Println(version.Lang(\u0026#34;go1.22rc1\u0026#34;)) // go1.22 fmt.Println(version.Lang(\u0026#34;1.22\u0026#34;)) // fmt.Println(version.Lang(\u0026#34;wrong\u0026#34;)) // fmt.Println(version.Lang(\u0026#34;\u0026#34;)) // } In the example above, we can see how the Lang function adapt the Go version string. It removes all minor versions and appearance of \u0026ldquo;release candide\u0026rdquo; phrase, and present them in the end as an official Go versions that we experienced in the past (and we might experience in the future). In cases where we provided an invalid, or empty string, the ending result will be also an empty string, as the Lang function can\u0026rsquo;t find the actual version name.\nOne interesting point, not just for the Long function, but, as you will see, for all functions in this package, to consider some string as a valid Go version, it needs to have a prefix go.\nIsValid # The next function we are examining is the IsValid function. This function checks a string with a potential Go version and returns a boolean result that tells us if the version is valid or not.\nIsValid function\nfunc IsValid(x string) bool As we can see the function signature above, function expects one argument, a string, that represents a Go version. An output should be a bool value, which tells us if the Go version is valid or not.\nIsValid function examples\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;go/version\u0026#34; ) func main() { fmt.Println(version.IsValid(\u0026#34;go1.0\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;go1\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;go1.22.4\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;go1.22.3\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;go1.22.2\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;go1.22.rc1\u0026#34;)) // false fmt.Println(version.IsValid(\u0026#34;go1.22rc1\u0026#34;)) // true fmt.Println(version.IsValid(\u0026#34;1.22\u0026#34;)) // false fmt.Println(version.IsValid(\u0026#34;wrong\u0026#34;)) // false fmt.Println(version.IsValid(\u0026#34;\u0026#34;)) // false } In the example above, we can see how the IsValid function checks validity of Go version string. In a way it represents a subcase of the Lang function: wherever we got an empty string in the Lang function, we got false as the result of the IsValid function.\nCompare # Finally, with the function Compare, we are showing the complete picture of the version package. This function provides the complex functionality for comparing different Go version in a spirit of other examples of the Compare function.\nCompare function\n// Compare returns // //\t-1 if x is less than y, //\t0 if x equals y, //\t+1 if x is greater than y. // ... func Compare(x, y string) int In the function signature, we can see that the Compare function accepts two strings as arguments, where both of them represent Go versions. The result is an integer value, which can be -1, 0 or 1, depending od the comparison result.\nCompare function examples\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;go/version\u0026#34; ) func main() { fmt.Println(version.Compare(\u0026#34;go1.0\u0026#34;, \u0026#34;go1\u0026#34;)) // 0 fmt.Println(version.Compare(\u0026#34;go1.1\u0026#34;, \u0026#34;go1.1.0\u0026#34;)) // 0 fmt.Println(version.Compare(\u0026#34;go1\u0026#34;, \u0026#34;\u0026#34;)) // 1 fmt.Println(version.Compare(\u0026#34;\u0026#34;, \u0026#34;\u0026#34;)) // 0 fmt.Println(version.Compare(\u0026#34;\u0026#34;, \u0026#34;go1\u0026#34;)) // -1 fmt.Println(version.Compare(\u0026#34;go1.22.4\u0026#34;, \u0026#34;go1.22.3\u0026#34;)) // 1 fmt.Println(version.Compare(\u0026#34;go1.22.2\u0026#34;, \u0026#34;go1.22.3\u0026#34;)) // -1 fmt.Println(version.Compare(\u0026#34;go1.22.2\u0026#34;, \u0026#34;go1.22rc1\u0026#34;)) // 1 fmt.Println(version.Compare(\u0026#34;go1.22rc2\u0026#34;, \u0026#34;go1.22rc1\u0026#34;)) // 1 fmt.Println(version.Compare(\u0026#34;go1.22.4\u0026#34;, \u0026#34;go1.21.4\u0026#34;)) // 1 fmt.Println(version.Compare(\u0026#34;go1.22rc1\u0026#34;, \u0026#34;go1.22rc1\u0026#34;)) // 1 } In the examples above, we can see how the Compare function actually works. First, it can find equality for two same versions, even if they are written in a different ways (the first two examples). Second, we can see that invalid versions are always considered as lower versions than any valid ones. So, we should be careful here with the comparison, as initial version validation might be important to do.\nFinally, for the correct versions, the Compare function is able to determine which one of them is a higher one, and provides such a result. This is not only applied for minor versions, but also for release candidate versions, as we can see in the last examples.\nConclusion # New version of Golang, 1.22, delivered many new updates, affecting standard library as well. In this article we checked how some functions from version packages work. Those new functions give us now possibility to validate and compare different Go version strings.\nUseful Resources # Go 1.22 is released! Go Open Source Project Go Dev ","date":"2 June 2024","externalUrl":null,"permalink":"/article/golang/standard-lib-version/","section":"Articles","summary":"With the release of Go 1.22, the Go standard library introduced several new features. As you might have noticed in articles related to the previous release, here we mostly concentrate on the new exciting packages and features that they give us. This article will start this journey, by providing a deeper look into the implementation of the version package in Go.\nLang # The first function we are ready to examine is the Lang function. This function provides a cleaned, valid Go version as a string. In case it can’t determine the actual version, due to an invalid state of the string value, it will return an string as a result.\nLang function\nfunc Lang(x string) string As we can see the function signature above, function expects one argument, a string, that represents a Go version. An output should be also one value, a string, as a cleaned Go version.\nLang function examples\npackage main import ( \"fmt\" \"go/version\" ) func main() { fmt.Println(version.Lang(\"go1.0\")) // go1 fmt.Println(version.Lang(\"go1\")) // go1 fmt.Println(version.Lang(\"go1.22.4\")) // go1.22 fmt.Println(version.Lang(\"go1.22.3\")) // go1.22 fmt.Println(version.Lang(\"go1.22.2\")) // go1.22 fmt.Println(version.Lang(\"go1.22.rc1\")) // fmt.Println(version.Lang(\"go1.22rc1\")) // go1.22 fmt.Println(version.Lang(\"1.22\")) // fmt.Println(version.Lang(\"wrong\")) // fmt.Println(version.Lang(\"\")) // } In the example above, we can see how the Lang function adapt the Go version string. It removes all minor versions and appearance of “release candide” phrase, and present them in the end as an official Go versions that we experienced in the past (and we might experience in the future). In cases where we provided an invalid, or empty string, the ending result will be also an empty string, as the Lang function can’t find the actual version name.\nOne interesting point, not just for the Long function, but, as you will see, for all functions in this package, to consider some string as a valid Go version, it needs to have a prefix go.\nIsValid # The next function we are examining is the IsValid function. This function checks a string with a potential Go version and returns a boolean result that tells us if the version is valid or not.\nIsValid function\nfunc IsValid(x string) bool As we can see the function signature above, function expects one argument, a string, that represents a Go version. An output should be a bool value, which tells us if the Go version is valid or not.\n","title":"Golang Release 1.22: version","type":"article"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/tags/golang-1.22/","section":"Tags","summary":"","title":"Golang-1.22","type":"tags"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/series/new-features-in-golang/","section":"Series","summary":"","title":"New Features in Golang","type":"series"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/tags/standard-lib/","section":"Tags","summary":"","title":"Standard-Lib","type":"tags"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/tags/validate/","section":"Tags","summary":"","title":"Validate","type":"tags"},{"content":"","date":"2 June 2024","externalUrl":null,"permalink":"/tags/version/","section":"Tags","summary":"","title":"Version","type":"tags"},{"content":"With the release of Go 1.21, the Go standard library introduced several new features. While we\u0026rsquo;ve already discussed some of them in previous articles, in this episode, we\u0026rsquo;ll dive into more advanced enhancements. Naturally, we\u0026rsquo;ll focus on the new functions designed for sorting slices, which are part of the new slices package. This article will provide a deeper look into the implementation of these three new functions and touch on benchmarking as well.\nSort # The Sort function is the first one we\u0026rsquo;d like to explore. This implementation is built upon the enhanced Pattern-defeating Quicksort, positioning it as one of the best-known unstable sorting algorithms. Don\u0026rsquo;t worry; we will discuss this \u0026ldquo;instability\u0026rdquo; aspect in this article. But first, let\u0026rsquo;s take a look at the function\u0026rsquo;s signature:\nSort function\nfunc Sort[S ~[]E, E cmp.Ordered](x S) As we\u0026rsquo;ve seen in some other articles, nearly all improvements in the Go standard library are built upon generics, a feature introduced in Go version 1.18, almost three years ago. Similar to other functions, the Sort function also expects a slice of a generic type as an argument, where each item must adhere to the Ordered constraint. The function doesn\u0026rsquo;t return a new value but sorts the original slice in place. Below, you\u0026rsquo;ll find some basic examples:\nSort function examples\nints := []int{1, 2, 3, 5, 5, 7, 9} slices.Sort(ints) fmt.Println(ints) // Output: // 1 2 3 5 5 7 9 ints2 := []int{9, 7, 5, 5, 3, 2, 1} slices.Sort(ints2) fmt.Println(ints2) // Output: // 1 2 3 5 5 7 9 floats := []float64{9, 3, 5, 7, 1, 2, 5} slices.Sort(floats) fmt.Println(floats) // Output: // 1 2 3 5 5 7 9 strings := []string{\u0026#34;3\u0026#34;, \u0026#34;9\u0026#34;, \u0026#34;2\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;1\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;5\u0026#34;} slices.Sort(strings) fmt.Println(strings) // Output: // 1 2 3 5 5 7 9 In the example above, we can observe the result of the Sort method. All the outputs consist of sorted slices, arranged in ascending order. However, what makes this function particularly intriguing is its ability to handle various data types using a single function, distinguishing it from the implementations we already possess in the sort package. Now that we\u0026rsquo;ve examined the results, let\u0026rsquo;s proceed to compare the performance benchmarks with the existing package.\nBenchmark # In this section, we aim to evaluate the performance of the new function by comparing it to the already existing sort package. Below, you\u0026rsquo;ll find the benchmark test results:\nBenchmark test cases\nconst cases = 1000 const size = 100000 var randomInts = (func() [][]int { var result [][]int for i := 1; i \u0026lt;= cases; i++ { result = append(result, makeRandomInts(size)) } return result })() var sortedInts = (func() [][]int { var result [][]int for i := 1; i \u0026lt;= cases; i++ { result = append(result, makeSortedInts(size)) } return result })() var reversedInts = (func() [][]int { var result [][]int for i := 1; i \u0026lt;= cases; i++ { result = append(result, makeReversedInts(size)) } return result })() func makeRandomInts(n int) []int { rand.Seed(42) ints := make([]int, n) for i := 0; i \u0026lt; n; i++ { ints[i] = rand.Intn(n) } return ints } func makeSortedInts(n int) []int { rand.Seed(42) start := rand.Intn(n) ints := make([]int, n) for i := 0; i \u0026lt; n; i++ { ints[i] = start + i } return ints } func makeReversedInts(n int) []int { rand.Seed(42) start := rand.Intn(n) ints := make([]int, n) for i := 0; i \u0026lt; n; i++ { ints[i] = start - i } return ints } First and foremost, we need to create our test cases, as shown in the example above. Here, we provide three functions for generating test cases: one with randomly distributed integers in slices, another with already sorted slices, and a third with reversely sorted slices. It\u0026rsquo;s crucial to note that before the tests begin, we generate all the test cases. That\u0026rsquo;s why we execute anonymous functions and store their results in global variables: randomInts, sortedInts, and reversedInts right at the start. By creating these test cases in the beginning, we ensure that our tests use the same predefined cases. You can find the actual test results below:\nBenchmark test functions\nfunc Benchmark_sort_RandomInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(randomInts[i%cases]) b.StartTimer() sort.Ints(testCase) } } func Benchmark_slices_RandomInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(randomInts[i%cases]) b.StartTimer() slices.Sort(testCase) } } func Benchmark_sort_SortedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(sortedInts[i%cases]) b.StartTimer() sort.Ints(testCase) } } func Benchmark_slices_SortedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(sortedInts[i%cases]) b.StartTimer() slices.Sort(testCase) } } func Benchmark_sort_ReversedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(reversedInts[i%cases]) b.StartTimer() sort.Ints(testCase) } } func Benchmark_slices_ReversedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(reversedInts[i%cases]) b.StartTimer() slices.Sort(testCase) } } // Output: // goos: darwin // goarch: amd64 // pkg: test // cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz // Benchmark_sort_RandomInts // Benchmark_sort_RandomInts-8 67\t15759326 ns/op // Benchmark_slices_RandomInts // Benchmark_slices_RandomInts-8 141\t8161744 ns/op // Benchmark_sort_SortedInts // Benchmark_sort_SortedInts-8 4279\t349835 ns/op // Benchmark_slices_SortedInts // Benchmark_slices_SortedInts-8 9817\t115025 ns/op // Benchmark_sort_ReversedInts // Benchmark_sort_ReversedInts-8 2364\t454587 ns/op // Benchmark_slices_ReversedInts // Benchmark_slices_ReversedInts-8 6807\t161754 ns/op // PASS You can observe the benchmarking results in the code above. In each iteration of the benchmarks, we used the test cases generated at the beginning of the test. However, we ensured to create clones of these test cases to prevent modifying the original ones, as all Sort functions work with slices by reference and modify the originals.\nThe outcome shows that, across all three groups of cases, the sorting solution from the new slices package is remarkably 2.5 times faster than the existing solution from the sort package. Personally, while I was hopeful for improved benchmarking in the new package, I didn\u0026rsquo;t anticipate such significantly better performance compared to the sort package.\nSortFunc and SortStableFunc # In this section, we will explore the SortFunc function and delve into the concept of instability introduced by this package. Before we proceed with the explanation, let\u0026rsquo;s take a look at the function\u0026rsquo;s signature:\nSortFunc function\nfunc SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) Similar to previous examples, the purpose of SortFunc here is to sort slices that may contain items of any type, not necessarily belonging to the Ordered constraint. However, it\u0026rsquo;s precisely in these scenarios, where we introduce sorting for more complex types, that we may encounter some instability, as demonstrated below:\nSortFunc function example\ntype TestStruct struct { Value int Name string } testData := []TestStruct{ { Value: 1, Name: \u0026#34;first\u0026#34;, }, { Value: 1, Name: \u0026#34;second\u0026#34;, }, { Value: 1, Name: \u0026#34;third\u0026#34;, }, { Value: 1, Name: \u0026#34;four\u0026#34;, }, { Value: 1, Name: \u0026#34;fifth\u0026#34;, }, { Value: 0, Name: \u0026#34;sixth\u0026#34;, }, { Value: 0, Name: \u0026#34;seventh\u0026#34;, }, { Value: 0, Name: \u0026#34;eight\u0026#34;, }, { Value: 0, Name: \u0026#34;ninth\u0026#34;, }, { Value: 0, Name: \u0026#34;tenth\u0026#34;, }, { Value: 2, Name: \u0026#34;eleventh\u0026#34;, }, { Value: 2, Name: \u0026#34;twelfth\u0026#34;, }, { Value: 2, Name: \u0026#34;thirteenth\u0026#34;, }, { Value: 2, Name: \u0026#34;fourteenth\u0026#34;, }, { Value: 2, Name: \u0026#34;fifteenth\u0026#34;, }, } for i := 0; i \u0026lt; 5; i++ { testCase := slices.Clone(testData) slices.SortFunc(testCase, func(a, b TestStruct) int { return cmp.Compare(a.Value, b.Value) }) fmt.Println(testCase) } // Output: // [{0 tenth} {0 sixth} {0 seventh} {0 ninth} {0 eight} {1 fifth} {1 second} {1 four} {1 third} {1 first} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 tenth} {0 sixth} {0 seventh} {0 ninth} {0 eight} {1 fifth} {1 second} {1 four} {1 third} {1 first} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 tenth} {0 sixth} {0 seventh} {0 ninth} {0 eight} {1 fifth} {1 second} {1 four} {1 third} {1 first} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 tenth} {0 sixth} {0 seventh} {0 ninth} {0 eight} {1 fifth} {1 second} {1 four} {1 third} {1 first} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 tenth} {0 sixth} {0 seventh} {0 ninth} {0 eight} {1 fifth} {1 second} {1 four} {1 third} {1 first} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] In the example above, we introduce a custom struct named TestStruct (quite unexpected, right?). It has two attributes: Value, which we\u0026rsquo;ll use in the comparison function to determine the item\u0026rsquo;s position in the sorted slice, and Name, which serves as a description for tracking items\u0026rsquo; positions in the slice after sorting, without influencing the sorting logic itself.\nFor our test data, we\u0026rsquo;ve created a slice comprising fifteen items (to reveal instability, a slice should have at least 13 items). In this slice, we have three groups of values (1, 2, and 3) with item names uniquely derived from ordered numbers (first to fifteenth). What were the results of execution? The sorting process consistently produced the correctly sorted slice, maintaining the same order of items. However, items that were supposed to change their positions during sorting didn\u0026rsquo;t preserve their initial order when their values were identical.\nFor instance, the item named sixth now appears as the second item, right after the tenth item. However, originally, sixth was added to the original slice before tenth. This exemplifies a critical aspect of instability introduced by SortFunc: it doesn\u0026rsquo;t randomly rearrange identical items with each execution. Instead, one slice will consistently have the same order after sorting, but SortFunc doesn\u0026rsquo;t guarantee that identical items will retain their original ordering.\nTo explore how we can address this issue, let\u0026rsquo;s take a look at the signature of another function:\nSortStableFunc function\nfunc SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) The signature of SortStableFunc is identical to that of SortFunc, so there\u0026rsquo;s nothing new in this regard. Let\u0026rsquo;s examine its behavior using the exact same test case we discussed earlier:\nSortStableFunc function example\n// .... for i := 0; i \u0026lt; 5; i++ { testCase := slices.Clone(testData) slices.SortStableFunc(testCase, func(a, b TestStruct) int { return cmp.Compare(a.Value, b.Value) }) fmt.Println(testCase) } // Output: // [{0 sixth} {0 seventh} {0 eight} {0 ninth} {0 tenth} {1 first} {1 second} {1 third} {1 four} {1 fifth} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 sixth} {0 seventh} {0 eight} {0 ninth} {0 tenth} {1 first} {1 second} {1 third} {1 four} {1 fifth} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 sixth} {0 seventh} {0 eight} {0 ninth} {0 tenth} {1 first} {1 second} {1 third} {1 four} {1 fifth} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 sixth} {0 seventh} {0 eight} {0 ninth} {0 tenth} {1 first} {1 second} {1 third} {1 four} {1 fifth} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] // [{0 sixth} {0 seventh} {0 eight} {0 ninth} {0 tenth} {1 first} {1 second} {1 third} {1 four} {1 fifth} {2 eleventh} {2 twelfth} {2 thirteenth} {2 fourteenth} {2 fifteenth}] In this instance, the concept of stability becomes quite evident. Concerning values, the SortStableFunc function also arranges items with the value 0 five times, followed by five occurrences of the value 1, and finally, five instances of the value 2. However, what distinguishes this function is that elements with the same value maintain the same order established during the initialization of the slice. With this function, we can anticipate the order of identical elements. But, are there any consequences to consider? Let\u0026rsquo;s revisit the benchmark to find out.\nBenchmark # To compare the performance of both functions, we\u0026rsquo;ll use the same setup as in the previous benchmark:\nBenchmark test functions\nfunc Benchmark_stable_RandomInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(randomInts[i%cases]) b.StartTimer() slices.SortStableFunc(testCase, cmp.Compare[int]) } } func Benchmark_unstable_RandomInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(randomInts[i%cases]) b.StartTimer() slices.SortFunc(testCase, cmp.Compare[int]) } } func Benchmark_stable_SortedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(sortedInts[i%cases]) b.StartTimer() slices.SortStableFunc(testCase, cmp.Compare[int]) } } func Benchmark_unstable_SortedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(sortedInts[i%cases]) b.StartTimer() slices.SortFunc(testCase, cmp.Compare[int]) } } func Benchmark_stable_ReversedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(reversedInts[i%cases]) b.StartTimer() slices.SortStableFunc(testCase, cmp.Compare[int]) } } func Benchmark_unstable_ReversedInts(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { b.StopTimer() testCase := slices.Clone(reversedInts[i%cases]) b.StartTimer() slices.SortFunc(testCase, cmp.Compare[int]) } } // Output: // goos: darwin // goarch: amd64 // pkg: test // cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz // Benchmark_stable_RandomInts // Benchmark_stable_RandomInts-8 33\t30398118 ns/op // Benchmark_unstable_RandomInts // Benchmark_unstable_RandomInts-8 84\t13595359 ns/op // Benchmark_stable_SortedInts // Benchmark_stable_SortedInts-8 2229\t509601 ns/op // Benchmark_unstable_SortedInts // Benchmark_unstable_SortedInts-8 3250\t308542 ns/op // Benchmark_stable_ReversedInts // Benchmark_stable_ReversedInts-8 285\t4087567 ns/op // Benchmark_unstable_ReversedInts // Benchmark_unstable_ReversedInts-8 3195\t370620 ns/op // PASS The outcome we observed shouldn\u0026rsquo;t come as a big surprise, right? After all, why would we need unstable sorting in the first place? As seen in the benchmark results, the efficiency of unstable sorting varies depending on the case, ranging from 50% faster than the stable variant to over 10 times faster! This vividly highlights the significance of having both stable and unstable versions of the function. It also provides us with guidance on how to proceed with these functions: always opt for the unstable one if you aren\u0026rsquo;t particularly concerned about the order of identical elements.\nConclusion # New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how some functions from slices packages work. Those new functions give us now possibility to more efficiently sort slices than ever before, by using the modern algorithms.\nUseful Resources # Go 1.21 is released! Go Open Source Project Go Dev ","date":"8 October 2023","externalUrl":null,"permalink":"/article/golang/standard-lib-slices-part2/","section":"Articles","summary":"With the release of Go 1.21, the Go standard library introduced several new features. While we’ve already discussed some of them in previous articles, in this episode, we’ll dive into more advanced enhancements. Naturally, we’ll focus on the new functions designed for sorting slices, which are part of the new slices package. This article will provide a deeper look into the implementation of these three new functions and touch on benchmarking as well.\nSort # The Sort function is the first one we’d like to explore. This implementation is built upon the enhanced Pattern-defeating Quicksort, positioning it as one of the best-known unstable sorting algorithms. Don’t worry; we will discuss this “instability” aspect in this article. But first, let’s take a look at the function’s signature:\nSort function\nfunc Sort[S ~[]E, E cmp.Ordered](x S) As we’ve seen in some other articles, nearly all improvements in the Go standard library are built upon generics, a feature introduced in Go version 1.18, almost three years ago. Similar to other functions, the Sort function also expects a slice of a generic type as an argument, where each item must adhere to the Ordered constraint. The function doesn’t return a new value but sorts the original slice in place. Below, you’ll find some basic examples:\nSort function examples\nints := []int{1, 2, 3, 5, 5, 7, 9} slices.Sort(ints) fmt.Println(ints) // Output: // 1 2 3 5 5 7 9 ints2 := []int{9, 7, 5, 5, 3, 2, 1} slices.Sort(ints2) fmt.Println(ints2) // Output: // 1 2 3 5 5 7 9 floats := []float64{9, 3, 5, 7, 1, 2, 5} slices.Sort(floats) fmt.Println(floats) // Output: // 1 2 3 5 5 7 9 strings := []string{\"3\", \"9\", \"2\", \"5\", \"1\", \"7\", \"5\"} slices.Sort(strings) fmt.Println(strings) // Output: // 1 2 3 5 5 7 9 In the example above, we can observe the result of the Sort method. All the outputs consist of sorted slices, arranged in ascending order. However, what makes this function particularly intriguing is its ability to handle various data types using a single function, distinguishing it from the implementations we already possess in the sort package. Now that we’ve examined the results, let’s proceed to compare the performance benchmarks with the existing package.\nBenchmark # In this section, we aim to evaluate the performance of the new function by comparing it to the already existing sort package. Below, you’ll find the benchmark test results:\n","title":"Golang Release 1.21: slices - Part 2","type":"article"},{"content":"","date":"8 October 2023","externalUrl":null,"permalink":"/tags/golang-1.21/","section":"Tags","summary":"","title":"Golang-1.21","type":"tags"},{"content":"","date":"8 October 2023","externalUrl":null,"permalink":"/tags/quick-sort/","section":"Tags","summary":"","title":"Quick-Sort","type":"tags"},{"content":"","date":"8 October 2023","externalUrl":null,"permalink":"/tags/slices/","section":"Tags","summary":"","title":"Slices","type":"tags"},{"content":"","date":"8 October 2023","externalUrl":null,"permalink":"/tags/sort/","section":"Tags","summary":"","title":"Sort","type":"tags"},{"content":"","date":"6 October 2023","externalUrl":null,"permalink":"/tags/binary-search/","section":"Tags","summary":"","title":"Binary-Search","type":"tags"},{"content":"As part of the new Go release, several exciting changes have been introduced to the Go ecosystem. While we\u0026rsquo;ve explored some of these changes in other articles about the maps package and the cmp package, there\u0026rsquo;s much more to discover beyond these two packages.\nIn this article, we\u0026rsquo;ll focus on the first part of the slices package, specifically its new search functionality. Like many other updates and newly introduced packages, this one is also built upon the foundation of generics, which were introduced in Go 1.18.\nBinarySearch and BinarySearchFunc # Let\u0026rsquo;s start by exploring the first pair of functions designed for efficiently searching a target value within sorted slices. In this context, we\u0026rsquo;re referring to the well-known Binary Search algorithm, which is renowned as one of the most significant algorithms and is frequently used in coding interviews. Below, you\u0026rsquo;ll find the signatures of both of these functions:\nBinarySearch function\nfunc BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) BinarySearchFunc function\nfunc BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) Looking at the signatures of both functions, we can identify some small differences between them, and these differences serve specific purposes. The first function, BinarySearch, expects two arguments. The first argument should be a slice of sorted items, and it must adhere to the Ordered constraint. When the items are ordered, the algorithm can efficiently compare them using the Compare function from the cmp package.\nOn the other hand, the second function, BinarySearchFunc, is more versatile. It allows searching within slices where the items don\u0026rsquo;t necessarily conform to the Ordered constraint. This flexibility is achieved by introducing a third argument, the comparison function. This function is responsible for comparing items and determining their order. It will be called by the BinarySearchFunc itself to make comparisons.\nBoth functions return two values. The first value is the index of the item within the slice, and the second is a boolean value indicating whether the item was found in the slice or not. Let\u0026rsquo;s explore some examples below:\nBinarySearch examples\nfmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, 5)) // Output: // 2 true fmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, 9)) // Output: // 5 false fmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, -5)) // Output: // 0 false fmt.Println(slices.BinarySearch([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;}, \u0026#34;5\u0026#34;)) // Output: // 2 true fmt.Println(slices.BinarySearch([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;8\u0026#34;}, \u0026#34;9\u0026#34;)) // Output: // 6 false fmt.Println(slices.BinarySearch([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;}, \u0026#34;4\u0026#34;)) // Output: // 2 false Take a close look at the results returned by the BinarySearch function, especially when the item doesn\u0026rsquo;t exist in the slice. In our examples, we encountered four such cases where the function returned 0, 2, 5, and 6. When the requested item isn\u0026rsquo;t present in the slice, the function indicates where it should be positioned if it were to be added to the slice. Since the slice is sorted, it\u0026rsquo;s possible to determine the appropriate position for the item within the slice.\nHere\u0026rsquo;s how it works:\nIf the target item is less than all other items in the slice, it should be placed at index 0. If the target item is greater than all other items, it should be positioned at the end of the slice, which falls outside the index range of the current slice. Otherwise, the function calculates the suitable position for the item within the slice. Now, let\u0026rsquo;s explore the cases for the other function, BinarySearchFunc.\nBinarySearchFunc examples\nfmt.Println(slices.BinarySearchFunc([]int{1, 3, 5, 6, 7}, 5, cmp.Compare[int])) // Output: // 2 true fmt.Println(slices.BinarySearchFunc([]int{1, 3, 5, 6, 7}, 9, cmp.Compare[int])) // Output: // 5 false fmt.Println(slices.BinarySearchFunc([]int{1, 3, 5, 6, 7}, -5, cmp.Compare[int])) // Output: // 0 false fmt.Println(slices.BinarySearchFunc([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;}, \u0026#34;5\u0026#34;, cmp.Compare[string])) // Output: // 2 true fmt.Println(slices.BinarySearchFunc([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;8\u0026#34;}, \u0026#34;9\u0026#34;, cmp.Compare[string])) // Output: // 6 false fmt.Println(slices.BinarySearchFunc([]string{\u0026#34;1\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;6\u0026#34;, \u0026#34;7\u0026#34;}, \u0026#34;4\u0026#34;, cmp.Compare[string])) // Output: // 2 false Here, we have simple examples using BinarySearchFunc, where we employed the same test cases as with the BinarySearch function. However, this time, we had to provide a comparison function as an argument. In this case, we utilized the Compare function from Go\u0026rsquo;s Standard Library.\nBut what\u0026rsquo;s the real purpose of BinarySearchFunc then? Let\u0026rsquo;s explore the example below to understand its significance.\nBinarySearchFunc complex example\ntype Exam struct { Content string Mark int } fmt.Println(slices.BinarySearchFunc([]Exam{ { Content: \u0026#34;First\u0026#34;, Mark: 1, }, { Content: \u0026#34;Second\u0026#34;, Mark: 1, }, { Content: \u0026#34;Third\u0026#34;, Mark: 2, }, { Content: \u0026#34;Fourth\u0026#34;, Mark: 3, }, }, Exam{ Content: \u0026#34;Third\u0026#34;, Mark: 2, }, func(exam1 Exam, exam2 Exam) int { compare := cmp.Compare(exam1.Mark, exam2.Mark) if compare == 0 { return cmp.Compare(exam1.Content, exam2.Content) } return compare })) // Output: // 2 true In this example, we can see the true significance of BinarySearchFunc. If we intend to search for an element in a slice that doesn\u0026rsquo;t consist of items of type Ordered, as in this case with a simple struct Exam, then we should use this method. We can define our own comparison function to adapt the provided binary search solution to handle our specific situation.\nMin and MinFunc # Now, let\u0026rsquo;s discuss something that we can easily understand just from the function names. Yes, we are talking about the functions Min and MinFunc. We have already discussed the built-in min and max functions, and the functions in the slices package heavily rely on them. But before we dive into further explanation, let\u0026rsquo;s take a look at their signatures:\nMin functions\nfunc Min[S ~[]E, E cmp.Ordered](x S) E MinFunc functions\nfunc MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E The purpose of both new functions is to find the minimum value in the slice provided as an argument to the function. In the case of the Min function, the slice must contain items of the Ordered type, while in the case of the MinFunc, there is no such requirement, but we must provide a comparison function as a second argument.\nExample of Min function\nfmt.Println(slices.Min([]int{1, 3, 9, 2, -1, 5, 7})) // Output: // -1 fmt.Println(slices.Min([]string{\u0026#34;bac\u0026#34;, \u0026#34;aaa\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;cccc\u0026#34;})) // Output: // a fmt.Println(slices.Min([]float64{1, 1, 1})) // Output: // 1 fmt.Println(slices.Min([]int{})) // Output: // panic: slices.Min: empty list Example of MinFunc function\nfmt.Println(slices.MinFunc([]int{1, 3, 9, 2, -1, 5, 7}, cmp.Compare[int])) // Output: // -1 fmt.Println(slices.MinFunc([]string{\u0026#34;bac\u0026#34;, \u0026#34;aaa\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;cccc\u0026#34;}, cmp.Compare[string])) // Output: // a fmt.Println(slices.MinFunc([]float64{1, 1, 1}, cmp.Compare[float64])) // Output: // 1 type Exam struct { Content string Mark int } fmt.Println(slices.MinFunc([]Exam{ { Content: \u0026#34;First\u0026#34;, Mark: 1, }, { Content: \u0026#34;Second\u0026#34;, Mark: 1, }, { Content: \u0026#34;Third\u0026#34;, Mark: 2, }, { Content: \u0026#34;Fourth\u0026#34;, Mark: 3, }, }, func(exam1 Exam, exam2 Exam) int { compare := cmp.Compare(exam1.Mark, exam2.Mark) if compare == 0 { return cmp.Compare(exam1.Content, exam2.Content) } return compare })) // Output: // {First 1} In the provided examples, we can see that both functions work as expected. Min works fine with all types that are of the Ordered type, and for other types, we should use MinFunc. One important note for these two methods is that both expect to receive a slice with at least one element as an argument. In case that doesn\u0026rsquo;t happen, both functions will panic, so make sure to handle this use case in your code.\nMax and MaxFunc # I\u0026rsquo;ll keep this section as brief as possible since this new pair of functions is a continuation of the previous pair. So, let\u0026rsquo;s take a look at them:\nMax functions\nfunc Max[S ~[]E, E cmp.Ordered](x S) E MaxFunc functions\nfunc MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E And followed immediately with some examples:\nExample of Min function\nfmt.Println(slices.Max([]int{1, 3, 9, 2, -1, 5, 7})) // Output: // 9 fmt.Println(slices.Max([]string{\u0026#34;bac\u0026#34;, \u0026#34;aaa\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;cccc\u0026#34;})) // Output: // cccc fmt.Println(slices.Max([]float64{1, 1, 1})) // Output: // 1 fmt.Println(slices.Max([]int{})) // Output: // panic: slices.Max: empty list Example of MinFunc function\nfmt.Println(slices.MaxFunc([]int{1, 3, 9, 2, -1, 5, 7}, cmp.Compare[int])) // Output: // -9 fmt.Println(slices.MaxFunc([]string{\u0026#34;bac\u0026#34;, \u0026#34;aaa\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;cccc\u0026#34;}, cmp.Compare[string])) // Output: // cccc fmt.Println(slices.MaxFunc([]float64{1, 1, 1}, cmp.Compare[float64])) // Output: // 1 type Exam struct { Content string Mark int } fmt.Println(slices.MaxFunc([]Exam{ { Content: \u0026#34;First\u0026#34;, Mark: 1, }, { Content: \u0026#34;Second\u0026#34;, Mark: 1, }, { Content: \u0026#34;Third\u0026#34;, Mark: 2, }, { Content: \u0026#34;Fourth\u0026#34;, Mark: 3, }, }, func(exam1 Exam, exam2 Exam) int { compare := cmp.Compare(exam1.Mark, exam2.Mark) if compare == 0 { return cmp.Compare(exam1.Content, exam2.Content) } return compare })) // Output: // {Fourth 3} As in all previous examples, here too, the Max and MaxFunc combo follows the same approach. Both functions search for the maximum value in the provided slice, which must have at least one item; otherwise, both functions will panic. Similar to the previous example, MaxFunc provides support for searching for the maximum in slices whose elements are not of the type Ordered, but you should also provide the comparison function as an argument.\nIsSorted and IsSortedFunc # Now, let\u0026rsquo;s discuss two slightly different functions: IsSorted and IsSortedFunc. Below, you can find their signatures:\nIsSorted function\nfunc IsSorted[S ~[]E, E cmp.Ordered](x S) bool IsSortedFunc function\nfunc IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool These two new functions check if the slices provided as arguments are sorted in ascending order. The only difference between them is that IsSorted, as in some previous examples, only works with slices of items that are of type Ordered, whereas the function IsSortedFunc accepts items of any type, but it is necessary that we provide our own comparison function as the second argument.\nExamples with IsSorted function\nfmt.Println(slices.IsSorted([]int{1, 2, 3, 5, 5, 7, 9})) // Output: // true fmt.Println(slices.IsSorted([]int{1, 3, 9, 2, -1, 5, 7})) // Output: // false fmt.Println(slices.IsSorted([]int{-1, 1, 2, 3, 5, 7, 9})) // Output: // true fmt.Println(slices.IsSorted([]int{9, 7, 5, 3, 2, 1, -1})) // Output: // false fmt.Println(slices.IsSorted([]int{})) // Output: // true fmt.Println(slices.IsSorted([]int(nil))) // Output: // true As we can observe, the IsSorted function returns a straightforward boolean value, indicating whether a slice is sorted in ascending order. It\u0026rsquo;s essential to handle empty slices as an edge case, where the function returns true. Therefore, this scenario should be appropriately addressed in the implementation. Similarly, we can utilize the IsSortedFunc function in a similar fashion:\nExamples with IsSortedFunc function\nfmt.Println(slices.IsSortedFunc([]int{1, 3, 9, 2, -1, 5, 7}, cmp.Compare[int])) // Output: // false fmt.Println(slices.IsSortedFunc([]string{\u0026#34;1\u0026#34;, \u0026#34;2\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;9\u0026#34;}, cmp.Compare[string])) // Output: // true fmt.Println(slices.IsSortedFunc([]string{\u0026#34;9\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;2\u0026#34;, \u0026#34;1\u0026#34;}, cmp.Compare[string])) // Output: // false fmt.Println(slices.IsSortedFunc([]string{\u0026#34;9\u0026#34;, \u0026#34;7\u0026#34;, \u0026#34;5\u0026#34;, \u0026#34;3\u0026#34;, \u0026#34;2\u0026#34;, \u0026#34;1\u0026#34;}, func(a, b string) int { return -1 * cmp.Compare(a, b) })) // Output: // true type Exam struct { Content string Mark int } fmt.Println(slices.IsSortedFunc([]Exam{ { Content: \u0026#34;First\u0026#34;, Mark: 1, }, { Content: \u0026#34;Second\u0026#34;, Mark: 1, }, { Content: \u0026#34;Third\u0026#34;, Mark: 2, }, { Content: \u0026#34;Fourth\u0026#34;, Mark: 3, }, }, func(exam1 Exam, exam2 Exam) int { compare := cmp.Compare(exam1.Mark, exam2.Mark) if compare == 0 { return cmp.Compare(exam1.Content, exam2.Content) } return compare })) // Output: // true Just like in all the previous examples involving functions that require custom comparison logic, there are no surprises here. The IsSortedFunc function allows us to check if a slice is sorted, without any requirement for the items to be of type Ordered. It\u0026rsquo;s worth noting that we can effortlessly check if a slice is sorted in descending order by providing our custom comparison function, which returns opposite values compared to the Compare function.\nConclusion # New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how some functions from slices packages work. Those new methods give us now possibility to easily check ordering of the items of any slice that we want.\nUseful Resources # Go 1.21 is released! Go Open Source Project Go Dev ","date":"6 October 2023","externalUrl":null,"permalink":"/article/golang/standard-lib-slices-part1/","section":"Articles","summary":"As part of the new Go release, several exciting changes have been introduced to the Go ecosystem. While we’ve explored some of these changes in other articles about the maps package and the cmp package, there’s much more to discover beyond these two packages.\nIn this article, we’ll focus on the first part of the slices package, specifically its new search functionality. Like many other updates and newly introduced packages, this one is also built upon the foundation of generics, which were introduced in Go 1.18.\nBinarySearch and BinarySearchFunc # Let’s start by exploring the first pair of functions designed for efficiently searching a target value within sorted slices. In this context, we’re referring to the well-known Binary Search algorithm, which is renowned as one of the most significant algorithms and is frequently used in coding interviews. Below, you’ll find the signatures of both of these functions:\nBinarySearch function\nfunc BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) BinarySearchFunc function\nfunc BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) Looking at the signatures of both functions, we can identify some small differences between them, and these differences serve specific purposes. The first function, BinarySearch, expects two arguments. The first argument should be a slice of sorted items, and it must adhere to the Ordered constraint. When the items are ordered, the algorithm can efficiently compare them using the Compare function from the cmp package.\nOn the other hand, the second function, BinarySearchFunc, is more versatile. It allows searching within slices where the items don’t necessarily conform to the Ordered constraint. This flexibility is achieved by introducing a third argument, the comparison function. This function is responsible for comparing items and determining their order. It will be called by the BinarySearchFunc itself to make comparisons.\nBoth functions return two values. The first value is the index of the item within the slice, and the second is a boolean value indicating whether the item was found in the slice or not. Let’s explore some examples below:\nBinarySearch examples\nfmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, 5)) // Output: // 2 true fmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, 9)) // Output: // 5 false fmt.Println(slices.BinarySearch([]int{1, 3, 5, 6, 7}, -5)) // Output: // 0 false fmt.Println(slices.BinarySearch([]string{\"1\", \"3\", \"5\", \"6\", \"7\"}, \"5\")) // Output: // 2 true fmt.Println(slices.BinarySearch([]string{\"1\", \"3\", \"5\", \"6\", \"7\", \"8\"}, \"9\")) // Output: // 6 false fmt.Println(slices.BinarySearch([]string{\"1\", \"3\", \"5\", \"6\", \"7\"}, \"4\")) // Output: // 2 false Take a close look at the results returned by the BinarySearch function, especially when the item doesn’t exist in the slice. In our examples, we encountered four such cases where the function returned 0, 2, 5, and 6. When the requested item isn’t present in the slice, the function indicates where it should be positioned if it were to be added to the slice. Since the slice is sorted, it’s possible to determine the appropriate position for the item within the slice.\n","title":"Golang Release 1.21: slices - Part 1","type":"article"},{"content":"","date":"6 October 2023","externalUrl":null,"permalink":"/tags/search/","section":"Tags","summary":"","title":"Search","type":"tags"},{"content":"As the new release of Go came this summer, many of us started to look for the improvements inside its ecosystem. Many new features were introduced, including updates to the tool command to support backward and forward compatibility. New packages appeared inside the Standard Library, including maps and slices. In this article we are covering improvements introduced with the new cmp package.\nThe new package offers three new functions. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The cmp package introduces new functions for comparing values of Ordered constraint.\nLet\u0026rsquo;s dive into each of them.\nOrdered constraint and Compare function # The constraint Ordered encompasses all types that support comparison operators for values, specifically, \u0026lt;, \u0026lt;=, \u0026gt;= and \u0026gt;. This includes all numeric types in Go, as well as strings.\nOrdered Constraint\ntype Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string } Once we understand what the Ordered constraint includes, we can focus on the first function from the cmp package, which is the Compare function. Below, you can find its signature:\nCompare Function\n// Compare returns // //\t-1 if x is less than y, //\t0 if x equals y, //\t+1 if x is greater than y. // ... func Compare[T Ordered](x, y T) int The signature, along with the function description, makes it much easier to understand. The Compare function expects two arguments of the same type, compares their values, and returns a result that represents the comparison status:\n-1 if the first argument is less than the second. 0 if the arguments\u0026rsquo; values are equal. 1 if the first argument is greater than the second. Let\u0026rsquo;s prove such claim:\nCompare numerals\nfmt.Println(cmp.Compare(1, 2)) // Output: // -1 fmt.Println(cmp.Compare(1, 1)) // Output: // 0 fmt.Println(cmp.Compare(2, 1)) // Output: // 1 Compare strings\nfmt.Println(cmp.Compare(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) // Output: // -1 fmt.Println(cmp.Compare(\u0026#34;qwe\u0026#34;, \u0026#34;qwe\u0026#34;)) // Output: // 0 fmt.Println(cmp.Compare(\u0026#34;abcde\u0026#34;, \u0026#34;abcc\u0026#34;)) // Output: // 1 Above, we can see practical examples of the Compare function for both numerals and strings. Indeed, the return values can only belong to the set of numbers {-1, 0, 1}, as defined in the description.\nFunction Less # In addition to the function Compare, we got another, similar function Less. Although it\u0026rsquo;s rather easy to understand what is used for, let\u0026rsquo;s check its signature:\nFunction Less\nfunc Less[T Ordered](x, y T) bool Again, this method expects two arguments of the same type that must adhere to the Ordered constraint. It returns the boolean value true if the first argument is less than the second one.\nLess function with numerals\nfmt.Println(cmp.Compare(1, 2)) // Output: // -1 fmt.Println(cmp.Compare(1, 1)) // Output: // 0 fmt.Println(cmp.Compare(2, 1)) // Output: // 1 Less function with strings\nfmt.Println(cmp.Less(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) // Output: // true fmt.Println(cmp.Less(\u0026#34;qwe\u0026#34;, \u0026#34;qwe\u0026#34;)) // Output: // false fmt.Println(cmp.Less(\u0026#34;abcde\u0026#34;, \u0026#34;abcc\u0026#34;)) // Output: // false Bonus: functions min and max # In addition to the functions mentioned in the cmp package, the new Go release introduced two new built-in functions: min and max. They are also based on Generics and can be used without importing any package, just like other built-in functions. Below, you can find their signatures:\nMin function\nfunc min[T cmp.Ordered](x T, y ...T) T Max function\nfunc max[T cmp.Ordered](x T, y ...T) T Both min and max functions are variadic functions, and they expect at least one argument. As you can see, only the x argument of type T is required, and the y argument is a trailing argument that can accept many or no values of the same type T. The result of these functions is a single value of the same type T, representing the minimum or maximum of all the values. Let\u0026rsquo;s check some examples:\nExamples with numerals\nfmt.Println(min(1, 2, 3)) // Output: // 1 fmt.Println(max(1, 2, 3)) // Output: // 3 fmt.Println(min(1)) // Output: // 1 fmt.Println(max(1)) // Output: // 1 Examples with strings\nfmt.Println(min(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) // Output: // abc fmt.Println(max(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) // Output: // def fmt.Println(min(\u0026#34;qwe\u0026#34;, \u0026#34;qwe\u0026#34;, \u0026#34;qwe\u0026#34;)) // Output: // qwe fmt.Println(max(\u0026#34;qwe\u0026#34;)) // Output: // qwe In all the examples above, we can see how the new functions behave in various situations. This includes their normal behavior with only one argument, as well as when more than two arguments are provided.\nConclusion # New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how functions from cmp packages work. Those new methods give us now possibility to easily compare any ordered types in Go.\nUseful Resources # Go 1.21 is released! Go Open Source Project Go Dev ","date":"22 September 2023","externalUrl":null,"permalink":"/article/golang/standard-lib-cmp/","section":"Articles","summary":"As the new release of Go came this summer, many of us started to look for the improvements inside its ecosystem. Many new features were introduced, including updates to the tool command to support backward and forward compatibility. New packages appeared inside the Standard Library, including maps and slices. In this article we are covering improvements introduced with the new cmp package.\nThe new package offers three new functions. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The cmp package introduces new functions for comparing values of Ordered constraint.\nLet’s dive into each of them.\nOrdered constraint and Compare function # The constraint Ordered encompasses all types that support comparison operators for values, specifically, \u003c, \u003c=, \u003e= and \u003e. This includes all numeric types in Go, as well as strings.\nOrdered Constraint\ntype Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string } Once we understand what the Ordered constraint includes, we can focus on the first function from the cmp package, which is the Compare function. Below, you can find its signature:\nCompare Function\n// Compare returns // //\t-1 if x is less than y, //\t0 if x equals y, //\t+1 if x is greater than y. // ... func Compare[T Ordered](x, y T) int The signature, along with the function description, makes it much easier to understand. The Compare function expects two arguments of the same type, compares their values, and returns a result that represents the comparison status:\n-1 if the first argument is less than the second. 0 if the arguments’ values are equal. 1 if the first argument is greater than the second. Let’s prove such claim:\nCompare numerals\nfmt.Println(cmp.Compare(1, 2)) // Output: // -1 fmt.Println(cmp.Compare(1, 1)) // Output: // 0 fmt.Println(cmp.Compare(2, 1)) // Output: // 1 Compare strings\nfmt.Println(cmp.Compare(\"abc\", \"def\")) // Output: // -1 fmt.Println(cmp.Compare(\"qwe\", \"qwe\")) // Output: // 0 fmt.Println(cmp.Compare(\"abcde\", \"abcc\")) // Output: // 1 Above, we can see practical examples of the Compare function for both numerals and strings. Indeed, the return values can only belong to the set of numbers {-1, 0, 1}, as defined in the description.\nFunction Less # In addition to the function Compare, we got another, similar function Less. Although it’s rather easy to understand what is used for, let’s check its signature:\n","title":"Golang Release 1.21: cmp","type":"article"},{"content":"Not too long ago, we witnessed a new release of our favorite programming language. The Go team didn\u0026rsquo;t disappoint us once again. They introduced numerous new features, including updates to the tool command to support backward and forward compatibility. As always, the standard library has received new updates, and the first one we\u0026rsquo;ll explore in this article is the new maps package.\nThe new package offers only five new functions (two additional ones were removed from the package: Values and Keys), but they provide significant value. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The map package clearly provides new tools for Go maps. In this particular case, it introduces new functions for checking map equality, deleting items from maps, copying items into maps, and cloning maps.\nLet\u0026rsquo;s dive into each of them.\nEqual and EqualFunc # First, let\u0026rsquo;s examine the pair of functions used to check map equality: Equal and EqualFunc. The first one is a straightforward function that checks the equality of two provided maps as function arguments. The second one allows you to pass an additional argument that defines how you plan to examine the equality of values inside the maps. Here are their signatures:\nFunction Equal\nfunc Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool Function EqualFunc\nfunc EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool The Equal function is easier to understand. It simply defines two generic types, M1 and M2, which represent maps of two other generic types, K and V. Obviously, K is for map keys, and it allows any comparable value. The second type is V, representing map values, and it also allows being of a comparable type.\nThe EqualFunc function is slightly more complicated. First, it doesn\u0026rsquo;t assume that the values in the maps are of the same type, nor do they have to be comparable. For that reason, it introduces an additional argument, which is an equality function for the values in the maps. This way, we can compare two maps that have the same keys but not the same values, and we can define the logic for comparing if they are equal.\nSimple usage of Equal function\nfirst := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, } second := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, } fmt.Println(maps.Equal(first, second)) // Output: // true third := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, } fourth := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;wrong\u0026#34;, } fmt.Println(maps.Equal(third, fourth)) // Output: // false In the example above, there are no surprises. We use four maps to test the Equal function. In the first case, two maps are equal, but in the second case, their values are not the same. The following example is also easy.\nAdditional usage of Equal function\nfunc main() { first := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, } second := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, } fmt.Println(maps.Equal(first, second)) // Output: // false third := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, } fourth := map[string]string{ \u0026#34;key1\u0026#34;: string([]rune{\u0026#39;v\u0026#39;, \u0026#39;a\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;1\u0026#39;}), } fmt.Println(maps.Equal(third, fourth)) // Output: // true } But what will happen if we pass the second map whose types don\u0026rsquo;t match the types of the first one? Let us check that:\nFunction Equal and different types\nfirst := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;true\u0026#34;, } second := map[string]interface{}{ \u0026#34;key1\u0026#34;: true, } fmt.Println(maps.Equal(first, second)) // Output: // M2 (type map[string]interface{}) does not satisfy ~map[K]V This case doesn\u0026rsquo;t even compile. In order to use the function Equal, we need to ensure that both maps are of the same types for their keys and values. And now, this is the case where we can employ the second function, EqualFunc:\nFunction EqualFunc and different types\nfirst := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;7\u0026#34;, } second := map[string]interface{}{ \u0026#34;key1\u0026#34;: true, \u0026#34;key2\u0026#34;: 7, } fmt.Println(maps.EqualFunc(first, second, func(v1 string, v2 interface{}) bool { return v1 == fmt.Sprint(v2) })) // Output: // true With the EqualFunc function, we can provide a third argument, a functions that we can use to compare values of two maps. If the keys are equal (and all keys must be present in both maps), our equality function will be called with values belonging to the same key in both maps. Notice that the types of arguments in the equality function match the types of the maps\u0026rsquo; values (v1 is of type string, like the values of the first map, and v2 is of type interface{}, like the values of the second map).\nNow, with these two functions, we are in a position to check the equality of two maps using the standard algorithm (where both key and value pairs must be equal), or we can provide our own algorithm for checking values\u0026rsquo; equality (as long as the keys are equal by type and value).\nClone and Copy # The next pair of functions we want to explore are Clone and Copy. Obviously, just by looking at their names, we can guess what they do: Clone creates an exact clone of the existing map, while Copy copies all key-value pairs from one map to another. Let\u0026rsquo;s examine their signatures:\nFunction Clone\nfunc Clone[M ~map[K]V, K comparable, V any](m M) M Function Copy\nfunc Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) The Clone function expects one argument that should be of type M, which is a map and returns a map of exactly the same type. This map type must have a key of type K, which is comparable, and can have any type V for values. The Copy function has the same expectations; it handles the M1 and M2 type, which both represent a map with K type for keys (comparable) and V type for values (any).\nLet us now check the examples for Clone function:\nSimple usage of function Clone\nfirst := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, } cloned := maps.Clone(first) fmt.Println(cloned) // Output: // map[key1:value1 key2:value2] first[\u0026#34;key1\u0026#34;] = \u0026#34;value1-change\u0026#34; fmt.Println(first) // Output: // map[key1:value1-change key2:value2] fmt.Println(cloned) // Output: // map[key1:value1 key2:value2] In the code snippet above, we can see that the Clone function indeed creates a new instance of a map with the same underlying types for keys and values as the original one. To ensure that we do not get a reference to the original map (as all maps are passed as references), the example also confirms that the cloned map is completely independent of its original. We demonstrated this by changing the original map, which did not affect the cloned one.\nSimple usage of function Copy\nfirst := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1-first\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2-first\u0026#34;, } second := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1-second\u0026#34;, \u0026#34;key3\u0026#34;: \u0026#34;value3-second\u0026#34;, } maps.Copy(second, first) fmt.Println(second) // Output: // map[key1:value1-first key2:value2-first key3:value3-second] In the example above, we can see how the Copy function works. It copies all key-value pairs from one map into the other. If both the source and destination maps have the same key, the value in the destination will be overridden by the value from the source map. Additionally, if the destination already contains some keys that are not defined in the source, their values will remain intact. The Copy function obviously relies on having both the source and destination maps of the same underlying types for keys and values, as copying data between incompatible types is not possible in Go.\nDeleteFunc # The last function in the new map package is DeleteFunc. We can already assume what this method does by comparing its name to some of the functions we checked previously in the article. However, let\u0026rsquo;s first examine its signature:\nFunction DeleteFunc\nfunc DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) In the previous example, the DeleteFunc function also handles the type M, which represents a map of key-value pairs, where the types are K for keys (comparable) and V for values (any). Additionally, besides expecting an argument of type M, it also requires a deletion function argument. This deletion function expects arguments of types K and V to determine if a map item should be deleted. Let\u0026rsquo;s examine the following example:\nSimple usage of function DeleteFunc\nholder := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, \u0026#34;key3\u0026#34;: \u0026#34;wrong\u0026#34;, \u0026#34;key4\u0026#34;: \u0026#34;wrong\u0026#34;, } maps.DeleteFunc(holder, func(k string, v string) bool { return v == \u0026#34;wrong\u0026#34; }) fmt.Println(holder) // Output: // map[key1:value1 key2:value2] As we can see in the example above, the deletion function criteria are based only on the values (in this case, if the value is equal to the string \u0026ldquo;wrong\u0026rdquo;). The result is the original map with all keys permanently deleted. But how can we manage to delete all keys? Perhaps something like the code snippet below:\nClear all with function DeleteFunc\nholder := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, \u0026#34;key3\u0026#34;: \u0026#34;wrong\u0026#34;, \u0026#34;key4\u0026#34;: \u0026#34;wrong\u0026#34;, } maps.DeleteFunc(holder, func(string, string) bool { return true }) fmt.Println(holder) // Output: // map[] In this example, the deletion function simply returns true for all key-value pairs, effectively deleting all keys from the original map. Besides this approach, we can also clear complete map by using builtin clear function:\nClear function\nholder := map[string]string{ \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, \u0026#34;key3\u0026#34;: \u0026#34;wrong\u0026#34;, \u0026#34;key4\u0026#34;: \u0026#34;wrong\u0026#34;, } clear(holder) fmt.Println(holder) // Output: // map[] Conclusion # New version of Golang, 1.21, delivered many new updates, affecting standard library as well. In this article we checked how functions from maps packages work. Those new methods give us now possibility to easily check equality of maps, clone and copy them, and delete their items.\nUseful Resources # Go 1.21 is released! Go Open Source Project Go Dev ","date":"21 September 2023","externalUrl":null,"permalink":"/article/golang/standard-lib-maps/","section":"Articles","summary":"Not too long ago, we witnessed a new release of our favorite programming language. The Go team didn’t disappoint us once again. They introduced numerous new features, including updates to the tool command to support backward and forward compatibility. As always, the standard library has received new updates, and the first one we’ll explore in this article is the new maps package.\nThe new package offers only five new functions (two additional ones were removed from the package: Values and Keys), but they provide significant value. All of them rely on Generics, a feature introduced in Go version 1.18, which has opened up possibilities for many new features. The map package clearly provides new tools for Go maps. In this particular case, it introduces new functions for checking map equality, deleting items from maps, copying items into maps, and cloning maps.\nLet’s dive into each of them.\nEqual and EqualFunc # First, let’s examine the pair of functions used to check map equality: Equal and EqualFunc. The first one is a straightforward function that checks the equality of two provided maps as function arguments. The second one allows you to pass an additional argument that defines how you plan to examine the equality of values inside the maps. Here are their signatures:\nFunction Equal\nfunc Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool Function EqualFunc\nfunc EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool The Equal function is easier to understand. It simply defines two generic types, M1 and M2, which represent maps of two other generic types, K and V. Obviously, K is for map keys, and it allows any comparable value. The second type is V, representing map values, and it also allows being of a comparable type.\nThe EqualFunc function is slightly more complicated. First, it doesn’t assume that the values in the maps are of the same type, nor do they have to be comparable. For that reason, it introduces an additional argument, which is an equality function for the values in the maps. This way, we can compare two maps that have the same keys but not the same values, and we can define the logic for comparing if they are equal.\nSimple usage of Equal function\nfirst := map[string]string{ \"key1\": \"value1\", } second := map[string]string{ \"key1\": \"value1\", } fmt.Println(maps.Equal(first, second)) // Output: // true third := map[string]string{ \"key1\": \"value1\", } fourth := map[string]string{ \"key1\": \"wrong\", } fmt.Println(maps.Equal(third, fourth)) // Output: // false In the example above, there are no surprises. We use four maps to test the Equal function. In the first case, two maps are equal, but in the second case, their values are not the same. The following example is also easy.\n","title":"Golang Release 1.21: maps","type":"article"},{"content":"","date":"21 September 2023","externalUrl":null,"permalink":"/tags/map/","section":"Tags","summary":"","title":"Map","type":"tags"},{"content":"My favorite part of software development is writing tests, whether they are unit tests or integration tests. I enjoy the process immensely. There\u0026rsquo;s a certain satisfaction in creating a test case that uncovers a function\u0026rsquo;s failure. It brings me joy to discover a bug during development, knowing that I\u0026rsquo;ve fixed it before anyone encounters it in a test environment or, worse, in production. Sometimes, I stay up late just to write more tests; it\u0026rsquo;s like a hobby. I even spent around 30 minutes on my wedding day writing unit tests for my personal project, but don\u0026rsquo;t tell my wife!\nThe only thing that used to bother me was dealing with integration issues between multiple Microservices. How could I ensure that two Microservices, each with specific versions, wouldn\u0026rsquo;t face integration problems? How could I be certain that a new version of a Microservice didn\u0026rsquo;t break its API interface, rendering it unusable for others? This information was crucial to have before launching extensive scenarios in our end-to-end testing pipeline. Otherwise, we\u0026rsquo;d end up waiting for an hour just to receive feedback that we\u0026rsquo;d broken the JSON schema.\nThen, one day in the office, I heard a rumor that we were planning to use Contract Testing. I quickly checked the first article I found, and I was amazed. It was a breakthrough.\nContract Testing # There are many excellent articles about Contract testing, but the one I like the most is from Pactflow. Contract testing ensures that two parties can communicate effectively by testing them in isolation to verify if both sides support the messages they exchange. One party, known as the Consumer, captures the communication with the other party, referred to as the Provider, and creates the Contract. This Contract serves as a specification for the expected requests from the Consumer and the responses from the Provider. Application code automatically generates Contracts, typically during the unit testing phase. Automatic creation ensures that each Contract accurately reflects the latest state of affairs.\nContract testing After the Consumer publishes the Contract, the Provider can use it. In its code, likely within unit tests, the Provider conducts Contract verification and publishes the results. In both phases of Contract testing, we work solely on one side, without any actual interaction with the other party. Essentially, we are ensuring that both parties can communicate with each other within their separate pipelines. As a result, the entire process is asynchronous and independent. If either of these two phases fails, both the Consumer and Provider must collaborate to resolve integration issues. In some cases, the Consumer may need to adapt its integrational code, while in others, the Provider may need to adjust its API.\nIt\u0026rsquo;s essential to note that Contract testing is NOT Schema testing. Schema testing is confined to one party without any connection to another. In contrast, Contract testing verifies interactions on both sides and ensures compatibility between desired versions of both parties. Additionally, Contract testing is NOT End-to-End testing. End-to-End testing involves testing a group of services running together, typically evaluating the entire system, from the UI down to storage. In contrast, Contract testing conducts tests against each service independently. These tests are isolated and do not require more than one service to be running simultaneously.\nNow, let\u0026rsquo;s delve deeper into what Contract testing entails.\nPACT # PACT is a tool designed for Contract testing, and we utilize it to facilitate the validation of communication between Consumers and Providers over the HTTP protocol. It also extends its support to the testing of message queues such as SQS, RabbitMQ, Kafka, and more.\nOn the consumer side, we create Contracts using the PACT DSL tailored for a specific programming language. These Contracts encompass interactions that define expected requests and their corresponding minimal responses.\nDuring the test execution, the Consumer sends requests to a Mock Provider, which employs the defined interactions to compare actual and expected HTTP requests. When the requests align, the Mock Provider returns the expected minimal response. This allows the Consumer to verify whether it meets the anticipated criteria.\nConsumer Testing On the Provider side, we employ the previously created Contracts to ascertain if the server can fulfill the expected requirements. The outcomes of this verification process can be published to maintain a record of which versions of Consumers and Providers are compatible.\nDuring Provider-side testing, a Mock Consumer is responsible for sending the expected request to the Provider. The Provider then assesses whether the incoming HTTP request aligns with the expectations and subsequently generates a response. In the final step, the Mock Consumer compares the actual response with the anticipated minimal response and delivers the result of the verification process.\nProvider Testing All Contracts and verification results can be stored on the PACT Broker. The PACT Broker is a tool that developers usually need to host and maintain themselves on most projects. Alternatively, a public option like PactFlow is available for use.\nSimple server and client in Go # To write the first Contract, we need to provide some code for a simple server and client. In this case, the server should have one endpoint, /users/:userId, for returning Users by their ID. The code generates the result to avoid the need for more complex logic, such as communication with a database.\nFile /pkg/server/server.go\ntype User struct { ID string `json:\u0026#34;id\u0026#34;` FirstName string `json:\u0026#34;firstName\u0026#34;` LastName string `json:\u0026#34;lastName\u0026#34;` } func GetUserByID(ctx *gin.Context) { id := ctx.Param(\u0026#34;userId\u0026#34;) ctx.JSON(http.StatusOK, User{ ID: id, FirstName: fmt.Sprintf(\u0026#34;first%s\u0026#34;, id), LastName: fmt.Sprintf(\u0026#34;last%s\u0026#34;, id), }) } File /cmd/main.go\nfunc main() { router := gin.Default() router.GET(\u0026#34;/users/:userId\u0026#34;, server.GetUserByID) router.Run(\u0026#34;:8080\u0026#34;) } The complete code for the server is split into two files: server.go and main.go. For this demonstration, I\u0026rsquo;ve used the Gin web framework for Go, but any other framework (or even no framework at all) would suffice. The client code is even simpler and consists of two files: client.go and main.go. It creates and sends a GET request to the /users/:userId endpoint. Upon receiving the result, it parses the JSON body into the User struct.\nFile /pkg/client/client.go\nfunc GetUserByID(host string, id string) (*server.User, error) { uri := fmt.Sprintf(\u0026#34;http://%s/users/%s\u0026#34;, host, id) resp, err := http.Get(uri) if err != nil { return nil, err } defer resp.Body.Close() var user server.User err = json.NewDecoder(resp.Body).Decode(\u0026amp;user) if err != nil { return nil, err } return \u0026amp;user, nil } File /cmd/main.go\nfunc main() { user, err := client.GetUserByID(\u0026#34;localhost:8080\u0026#34;, \u0026#34;1\u0026#34;) if err != nil { panic(err) } fmt.Println(user) } In both the client and server code, I have included dedicated functions for handling requests and responses. This code structure is crucial to facilitate the creation of specific test functions for the code we intend to test later.\nPACT test for the client # Writing a PACT test in Go is similar to writing unit tests. In this case, we should also depend on the [package](https://github.com/pact-foundation/pact-go \u0026ldquo;package) from the Pact Foundation. Installation is a straightforward process, and it supports Go Modules.\nFile /pkg/client/client_test.go\nimport ( // // some imports // \u0026#34;github.com/pact-foundation/pact-go/types\u0026#34; \u0026#34;github.com/pact-foundation/pact-go/dsl\u0026#34; ) func TestClientPact_Local(t *testing.T) { // initialize PACT DSL pact := dsl.Pact{ Consumer: \u0026#34;example-client\u0026#34;, Provider: \u0026#34;example-server\u0026#34;, } // setup a PACT Mock Server pact.Setup(true) t.Run(\u0026#34;get user by id\u0026#34;, func(t *testing.T) { id := \u0026#34;1\u0026#34; pact. AddInteraction(). // specify PACT interaction Given(\u0026#34;User Alice exists\u0026#34;). // specify Provider state UponReceiving(\u0026#34;User \u0026#39;Alice\u0026#39; is requested\u0026#34;). // specify test case name WithRequest(dsl.Request{ // specify expected request Method: \u0026#34;GET\u0026#34;, // specify matching for endpoint Path: dsl.Term(\u0026#34;/users/1\u0026#34;, \u0026#34;/users/[0-9]+\u0026#34;), }). WillRespondWith(dsl.Response{ // specify minimal expected response Status: 200, Body: dsl.Like(server.User{ // pecify matching for response body ID: id, FirstName: \u0026#34;Alice\u0026#34;, LastName: \u0026#34;Doe\u0026#34;, }), }) // verify interaction on client side err := pact.Verify(func() error { // specify host anf post of PACT Mock Server as actual server host := fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, pact.Host, pact.Server.Port) // execute function user, err := GetUserByID(host, id) if err != nil { return errors.New(\u0026#34;error is not expected\u0026#34;) } // check if actual user is equal to expected if user == nil || user.ID != id { return fmt.Errorf(\u0026#34;expected user with ID %s but got %v\u0026#34;, id, user) } return err }) if err != nil { t.Fatal(err) } }) // write Contract into file if err := pact.WritePact(); err != nil { t.Fatal(err) } // stop PACT mock server pact.Teardown() } As you can see in the example above, I have provided the PACT test in Go as a simple unit test. At the beginning of the test, I have defined a PACT DSL and run the Mock Server. The critical point of the test is defining an Interaction. An Interaction contains the state of the Provider, the name of a test case, the expected request, and the expected minimal response. We can define many attributes for both the request and response, including body, headers, query, status code, etc.\nAfter defining the Interaction, the next step is Verification. The PACT test runs our client, which now sends a request to the PACT Mock Server instead of a real one. I have ensured this by providing the Mock Server\u0026rsquo;s host to the GetUserByID method. If the actual request matches the expected one, the Mock Server sends back the expected minimal response. Inside the test, we can make a final check if our method returns the correct User after extracting it from the JSON body.\nThe last step involves writing the Interaction in the form of a Contract. PACT stores the Contract inside the pacts folder by default, but we can change that during PACT DSL initialization. After executing the code, the final output should look like this:\nClient testing console output\n=== RUN TestClientPact_Local 2021/08/29 13:55:25 [INFO] checking pact-mock-service within range \u0026gt;= 3.5.0, \u0026lt; 4.0.0 2021/08/29 13:55:26 [INFO] checking pact-provider-verifier within range \u0026gt;= 1.31.0, \u0026lt; 2.0.0 2021/08/29 13:55:26 [INFO] checking pact-broker within range \u0026gt;= 1.22.3 2021/08/29 13:55:27 [INFO] INFO WEBrick 1.4.2 2021/08/29 13:55:27 [INFO] INFO ruby 2.6.3 (2019-04-16) [universal.x86_64-darwin19] 2021/08/29 13:55:27 [INFO] INFO WEBrick::HTTPServer#start: pid=21959 port=56423 --- PASS: TestClientPact_Local (2.31s) === RUN TestClientPact_Local/get_user_by_id 2021/08/29 13:55:27 [INFO] INFO going to shutdown ... 2021/08/29 13:55:28 [INFO] INFO WEBrick::HTTPServer#start done. --- PASS: TestClientPact_Local/get_user_by_id (0.02s) PASS PACT test for the server # Writing PACT tests for the server is easier. The idea is to verify the desired Contracts that the client already provides. We also write PACT tests for servers in the form of unit tests.\nFile /pkg/server/server_test.go\nimport ( // // some imports // \u0026#34;github.com/pact-foundation/pact-go/types\u0026#34; \u0026#34;github.com/pact-foundation/pact-go/dsl\u0026#34; ) func TestServerPact_Verification(t *testing.T) { // initialize PACT DSL pact := dsl.Pact{ Provider: \u0026#34;example-server\u0026#34;, } // verify Contract on server side _, err := pact.VerifyProvider(t, types.VerifyRequest{ ProviderBaseURL: \u0026#34;http://127.0.0.1:8080\u0026#34;, PactURLs: []string{\u0026#34;../client/pacts/example-client-example-server.json\u0026#34;}, }) if err != nil { t.Log(err) } } The client has already provided a Contract as a JSON file that contains all interactions. Here, we also need to define PACT DSL and then execute the verification of the Contract. During the Contract\u0026rsquo;s verification, PACT Mock Client sends expected requests to the server, specified in one of the Interactions in the Contract. The server receives the request and returns the actual response. Mock Client gets the response and matches it with the expected minimal response. If the whole process of verification is successful, we should get an output similar to this:\nServer testing console output\n=== RUN TestServerPact_Verification 2021/08/29 14:41:13 [INFO] checking pact-mock-service within range \u0026gt;= 3.5.0, \u0026lt; 4.0.0 2021/08/29 14:41:14 [INFO] checking pact-provider-verifier within range \u0026gt;= 1.31.0, \u0026lt; 2.0.0 2021/08/29 14:41:14 [INFO] checking pact-broker within range \u0026gt;= 1.22.3 --- PASS: TestServerPact_Verification (2.45s) === RUN TestServerPact_Verification/Pact_between__and__ --- PASS: TestServerPact_Verification/Pact_between__and__ (0.00s) === RUN TestServerPact_Verification/has_status_code_200 pact.go:637: Verifying a pact between example-client and example-server Given User Alice exists User \u0026#39;Alice\u0026#39; is requested with GET /users/1 returns a response which has status code 200 --- PASS: TestServerPact_Verification/has_status_code_200 (0.00s) === RUN TestServerPact_Verification/has_a_matching_body pact.go:637: Verifying a pact between example-client and example-server Given User Alice exists User \u0026#39;Alice\u0026#39; is requested with GET /users/1 returns a response which has a matching body --- PASS: TestServerPact_Verification/has_a_matching_body (0.00s) PASS Usage of PACT Broker with PactFlow # As mentioned earlier in this article, the usage of PACT testing cannot be completed without the PACT Broker. We do not expect to have access to Contracts in physical files from clients inside the pipelines of our servers. For that purpose, development teams should use a standalone PACT Broker dedicated to that project. It is possible to use the Docker image provided by PACT Foundation and have it installed as part of your infrastructure. Also, if you are willing to pay for a PACT Broker, the solution from PactFlow is perfect, and registration is simple. For this article, I have been using the trial version of PactFlow, which allows me to store up to five Contracts.\nPactFlow overview To publish Contracts to PactFlow, I need to make minor adaptations inside the client test. These adaptations include the new part where I have defined a PACT Publisher that uploads all Contracts after the test execution.\nAdapted File /pkg/client/client_test.go\nfunc TestClientPact_Broker(t *testing.T) { pact := dsl.Pact{ Consumer: \u0026#34;example-client\u0026#34;, Provider: \u0026#34;example-server\u0026#34;, } t.Run(\u0026#34;get user by id\u0026#34;, func(t *testing.T) { id := \u0026#34;1\u0026#34; pact. AddInteraction(). Given(\u0026#34;User Alice exists\u0026#34;). UponReceiving(\u0026#34;User \u0026#39;Alice\u0026#39; is requested\u0026#34;). WithRequest(dsl.Request{ Method: \u0026#34;GET\u0026#34;, Path: dsl.Term(\u0026#34;/users/1\u0026#34;, \u0026#34;/users/[0-9]+\u0026#34;), }). // // PACT verification // }) if err := pact.WritePact(); err != nil { t.Fatal(err) } // specify PACT publisher publisher := dsl.Publisher{} err := publisher.Publish(types.PublishRequest{ // a folder with all PACT test PactURLs: []string{\u0026#34;./pacts/\u0026#34;}, // PACT broker URI PactBroker: \u0026#34;\u0026lt;PACT BROKER\u0026gt;\u0026#34;, // API token for PACT broker BrokerToken: \u0026#34;\u0026lt;API TOKEN\u0026gt;\u0026#34;, ConsumerVersion: \u0026#34;1.0.0\u0026#34;, Tags: []string{\u0026#34;1.0.0\u0026#34;, \u0026#34;latest\u0026#34;}, }) if err != nil { t.Fatal(err) } pact.Teardown() } After registering with PactFlow, you should receive the new host for your PACT Broker. Additionally, you should use your API token to complete the Publisher definition in the code. You can find API tokens within the dashboard overview settings. When we execute the new client test, it adds the first Contract to PactFlow. This Contract has tags 1.0.0, latest, and master (which are added by default).\nPactFlow first contract To create a difference in the client, I adapted the test to send a request to the other endpoint /user/:userId instead of /users/:userId. Additionally, I changed the Tag and ConsumerVersion to be 1.0.1 instead of 1.0.0. After executing the test, the additional Contract will appear.\nPactFlow second contract Next, I adapted the server test to accommodate the changes. The new adjustments are made in the Verification process. It now includes the PACT Broker host, API token, and a decision to publish the verification result.\nAdapted File /pkg/server/server_test.go\nfunc TestServerPact_BrokerVerification(t *testing.T) { pact := dsl.Pact{ Provider: \u0026#34;example-server\u0026#34;, } _, err := pact.VerifyProvider(t, types.VerifyRequest{ BrokerURL: \u0026#34;\u0026lt;PACT BROKER\u0026gt;\u0026#34;, BrokerToken: \u0026#34;\u0026lt;API TOKEN\u0026gt;\u0026#34;, ProviderBaseURL: \u0026#34;http://127.0.0.1:8080\u0026#34;, ProviderVersion: \u0026#34;1.0.0\u0026#34;, ConsumerVersionSelectors: []types.ConsumerVersionSelector{ { Consumer: \u0026#34;example-client\u0026#34;, Tag: \u0026#34;1.0.0\u0026#34;, }, }, PublishVerificationResults: true, // publish results of verification to PACT broker }) if err != nil { t.Log(err) } } In addition, it should also include a selector for the Consumer name and version to perform verification with the correct Contract version. The first execution, which passed successfully, checks the client version 1.0.0. However, the second execution of the test, which failed, is for checking the client version 1.0.1. The second execution is expected to fail because the server still listens to the /users/:userId endpoint.\nPactFlow first verification To fix the integration between the newest client and server, we need to make adjustments to either of them. In this case, I have decided to modify the server to listen to the new /user/:usersId endpoint. After updating the server to the new version 1.0.1 and executing PACT verification once again, the test passes successfully, and it publishes new verification results on the PACT Broker.\nPactFlow second verification On PactFlow, as well as on any other PACT Broker, we can review the history of each Contract\u0026rsquo;s verification process by accessing a specific Contract from the dashboard overview and then examining its Matrix tab.\nPactFlow matrix Conclusion # Writing PACT tests is a fast and cost-effective way to incorporate into our pipeline. By validating Consumers and Providers early in our CI/CD process, we save time and receive early feedback on our integration outcomes. Contract tests enable us to utilize the current versions of our clients and servers, evaluating them independently to determine their compatibility.\nUseful Resources # Martin Fowler PactFlow ","date":"20 September 2023","externalUrl":null,"permalink":"/article/golang/tutorial-contract-testing-pact/","section":"Articles","summary":"My favorite part of software development is writing tests, whether they are unit tests or integration tests. I enjoy the process immensely. There’s a certain satisfaction in creating a test case that uncovers a function’s failure. It brings me joy to discover a bug during development, knowing that I’ve fixed it before anyone encounters it in a test environment or, worse, in production. Sometimes, I stay up late just to write more tests; it’s like a hobby. I even spent around 30 minutes on my wedding day writing unit tests for my personal project, but don’t tell my wife!\nThe only thing that used to bother me was dealing with integration issues between multiple Microservices. How could I ensure that two Microservices, each with specific versions, wouldn’t face integration problems? How could I be certain that a new version of a Microservice didn’t break its API interface, rendering it unusable for others? This information was crucial to have before launching extensive scenarios in our end-to-end testing pipeline. Otherwise, we’d end up waiting for an hour just to receive feedback that we’d broken the JSON schema.\nThen, one day in the office, I heard a rumor that we were planning to use Contract Testing. I quickly checked the first article I found, and I was amazed. It was a breakthrough.\nContract Testing # There are many excellent articles about Contract testing, but the one I like the most is from Pactflow. Contract testing ensures that two parties can communicate effectively by testing them in isolation to verify if both sides support the messages they exchange. One party, known as the Consumer, captures the communication with the other party, referred to as the Provider, and creates the Contract. This Contract serves as a specification for the expected requests from the Consumer and the responses from the Provider. Application code automatically generates Contracts, typically during the unit testing phase. Automatic creation ensures that each Contract accurately reflects the latest state of affairs.\nContract testing After the Consumer publishes the Contract, the Provider can use it. In its code, likely within unit tests, the Provider conducts Contract verification and publishes the results. In both phases of Contract testing, we work solely on one side, without any actual interaction with the other party. Essentially, we are ensuring that both parties can communicate with each other within their separate pipelines. As a result, the entire process is asynchronous and independent. If either of these two phases fails, both the Consumer and Provider must collaborate to resolve integration issues. In some cases, the Consumer may need to adapt its integrational code, while in others, the Provider may need to adjust its API.\n","title":"Golang Tutorial: Contract Testing with PACT","type":"article"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/tags/testing/","section":"Tags","summary":"","title":"Testing","type":"tags"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/series/testing-in-golang/","section":"Series","summary":"","title":"Testing in Golang","type":"series"},{"content":"This Cookie Policy explains how Ompluscator\u0026rsquo;s Blog (\u0026ldquo;Company,\u0026rdquo; \u0026ldquo;we,\u0026rdquo; \u0026ldquo;us,\u0026rdquo; and \u0026ldquo;our\u0026rdquo;) uses cookies and similar technologies to recognize you when you visit our website at https://www.ompluscator.io (\u0026ldquo;Website\u0026rdquo;). It explains what these technologies are and why we use them, as well as your rights to control our use of them.\nIn some cases we may use cookies to collect personal information, or that becomes personal information if we combine it with other information.\nWhat are cookies? # Cookies are small data files that are placed on your computer or mobile device when you visit a website. Cookies are widely used by website owners in order to make their websites work, or to work more efficiently, as well as to provide reporting information.\nCookies set by the website owner (in this case, Ompluscator\u0026rsquo;s Blog) are called \u0026ldquo;first-party cookies.\u0026rdquo; Cookies set by parties other than the website owner are called \u0026ldquo;third-party cookies.\u0026rdquo; Third-party cookies enable third-party features or functionality to be provided on or through the website (e.g., advertising, interactive content, and analytics). The parties that set these third-party cookies can recognize your computer both when it visits the website in question and also when it visits certain other websites.\nWhy do we use cookies? # We use first- and third-party cookies for several reasons. Some cookies are required for technical reasons in order for our Website to operate, and we refer to these as \u0026ldquo;essential\u0026rdquo; or \u0026ldquo;strictly necessary\u0026rdquo; cookies. Other cookies also enable us to track and target the interests of our users to enhance the experience on our Online Properties. Third parties serve cookies through our Website for advertising, analytics, and other purposes. This is described in more detail below.\nHow can I control cookies? # You have the right to decide whether to accept or reject cookies. You can exercise your cookie rights by setting your preferences in the Cookie Consent Manager. The Cookie Consent Manager allows you to select which categories of cookies you accept or reject. Essential cookies cannot be rejected as they are strictly necessary to provide you with services.\nThe Cookie Consent Manager can be found in the notification banner and on our website. If you choose to reject cookies, you may still use our website though your access to some functionality and areas of our website may be restricted. You may also set or amend your web browser controls to accept or refuse cookies.\nThe specific types of first- and third-party cookies served through our Website and the purposes they perform are described in the table below (please note that the specific cookies served may vary depending on the specific Online Properties you visit):\nAnalytics and customization cookies # These cookies collect information that is used either in aggregate form to help us understand how our Website is being used or how effective our marketing campaigns are, or to help us customize our Website for you.\nName:_ga Purpose:Records a particular ID used to come up with data about website usage by the user Provider:.ompluscator.io Service:Google Analytics View Service Privacy Policy Country:United States Type:http_cookie Expires in:1 year 1 month 4 days Name:_ga_# Purpose:Used to distinguish individual users by means of designation of a randomly generated number as client identifier, which allows calculation of visits and sessions Provider:.ompluscator.io Service:Google Analytics View Service Privacy Policy Country:United States Type:http_cookie Expires in:1 year 1 month 4 days Name:__gpi Purpose:Tracks the user\u0026#39;s interaction with Google products Provider:.ompluscator.io Service:Google View Service Privacy Policy Country:United States Type:http_cookie Expires in:1 year 24 days Advertising cookies: # These cookies are used to make advertising messages more relevant to you. They perform functions like preventing the same ad from continuously reappearing, ensuring that ads are properly displayed for advertisers, and in some cases selecting advertisements that are based on your interests.\nName:__gads Purpose:Set by Google Ad Manager on a site to help with measuring how a user interacts with the ads on that domain and preventing the same ads from being shown to the user too many times. Provider:.ompluscator.io Service:Google AD Manager View Service Privacy Policy Country:United States Type:http_cookie Expires in:1 year 24 days Name:test_cookie Purpose:A session cookie used to check if the user’s browser supports cookies. Provider:.doubleclick.net Service:DoubleClick View Service Privacy Policy Country:United States Type:server_cookie Expires in:15 minutes Unclassified cookies: # These are cookies that have not yet been categorized. We are in the process of classifying these cookies with the help of their providers.\nName:rc::h Purpose:**Ompluscator\u0026#39;s Blog** Provider:www.google.com Service:**Ompluscator\u0026#39;s Blog** Country:United States Type:html_local_storage Expires in:persistent How can I control cookies on my browser? # As the means by which you can refuse cookies through your web browser controls vary from browser to browser, you should visit your browser\u0026rsquo;s help menu for more information. The following is information about how to manage cookies on the most popular browsers:\nChrome Internet Explorer Firefox Safari Edge Opera In addition, most advertising networks offer you a way to opt out of targeted advertising. If you would like to find out more information, please visit:\nDigital Advertising Alliance Digital Advertising Alliance of Canada European Interactive Digital Advertising Alliance What about other tracking technologies, like web beacons? # Cookies are not the only way to recognize or track visitors to a website. We may use other, similar technologies from time to time, like web beacons (sometimes called \u0026ldquo;tracking pixels\u0026rdquo; or \u0026ldquo;clear gifs\u0026rdquo;). These are tiny graphics files that contain a unique identifier that enables us to recognize when someone has visited our Website or opened an email including them. This allows us, for example, to monitor the traffic patterns of users from one page within a website to another, to deliver or communicate with cookies, to understand whether you have come to the website from an online advertisement displayed on a third-party website, to improve site performance, and to measure the success of email marketing campaigns. In many instances, these technologies are reliant on cookies to function properly, and so declining cookies will impair their functioning.\nDo you use Flash cookies or Local Shared Objects? # Websites may also use so-called \u0026ldquo;Flash Cookies\u0026rdquo; (also known as Local Shared Objects or \u0026ldquo;LSOs\u0026rdquo;) to, among other things, collect and store information about your use of our services, fraud prevention, and for other site operations.\nIf you do not want Flash Cookies stored on your computer, you can adjust the settings of your Flash player to block Flash Cookies storage using the tools contained in the Website Storage Settings Panel. You can also control Flash Cookies by going to the Global Storage Settings Panel and following the instructions (which may include instructions that explain, for example, how to delete existing Flash Cookies (referred to \u0026ldquo;information\u0026rdquo; on the Macromedia site), how to prevent Flash LSOs from being placed on your computer without your being asked, and (for Flash Player 8 and later) how to block Flash Cookies that are not being delivered by the operator of the page you are on at the time).\nPlease note that setting the Flash Player to restrict or limit acceptance of Flash Cookies may reduce or impede the functionality of some Flash applications, including, potentially, Flash applications used in connection with our services or online content.\nDo you serve targeted advertising? # Third parties may serve cookies on your computer or mobile device to serve advertising through our Website. These companies may use information about your visits to this and other websites in order to provide relevant advertisements about goods and services that you may be interested in. They may also employ technology that is used to measure the effectiveness of advertisements. They can accomplish this by using cookies or web beacons to collect information about your visits to this and other sites in order to provide relevant advertisements about goods and services of potential interest to you. The information collected through this process does not enable us or them to identify your name, contact details, or other details that directly identify you unless you choose to provide these.\nHow often will you update this Cookie Policy? # We may update this Cookie Policy from time to time in order to reflect, for example, changes to the cookies we use or for other operational, legal, or regulatory reasons. Please therefore revisit this Cookie Policy regularly to stay informed about our use of cookies and related technologies.\nThe date at the top of this Cookie Policy indicates when it was last updated.\nWhere can I get further information? # If you have any questions about our use of cookies or other technologies, please email us at marko.milojevic@ompluscator.io.\nThis cookie policy was created using Termly\u0026rsquo;s Cookie Consent Manager.\n","date":"20 September 2023","externalUrl":null,"permalink":"/general/cookie-policy/","section":"Generals","summary":"This Cookie Policy explains how Ompluscator’s Blog (“Company,” “we,” “us,” and “our”) uses cookies and similar technologies to recognize you when you visit our website at https://www.ompluscator.io (“Website”). It explains what these technologies are and why we use them, as well as your rights to control our use of them.\nIn some cases we may use cookies to collect personal information, or that becomes personal information if we combine it with other information.\nWhat are cookies? # Cookies are small data files that are placed on your computer or mobile device when you visit a website. Cookies are widely used by website owners in order to make their websites work, or to work more efficiently, as well as to provide reporting information.\nCookies set by the website owner (in this case, Ompluscator’s Blog) are called “first-party cookies.” Cookies set by parties other than the website owner are called “third-party cookies.” Third-party cookies enable third-party features or functionality to be provided on or through the website (e.g., advertising, interactive content, and analytics). The parties that set these third-party cookies can recognize your computer both when it visits the website in question and also when it visits certain other websites.\nWhy do we use cookies? # We use first- and third-party cookies for several reasons. Some cookies are required for technical reasons in order for our Website to operate, and we refer to these as “essential” or “strictly necessary” cookies. Other cookies also enable us to track and target the interests of our users to enhance the experience on our Online Properties. Third parties serve cookies through our Website for advertising, analytics, and other purposes. This is described in more detail below.\nHow can I control cookies? # You have the right to decide whether to accept or reject cookies. You can exercise your cookie rights by setting your preferences in the Cookie Consent Manager. The Cookie Consent Manager allows you to select which categories of cookies you accept or reject. Essential cookies cannot be rejected as they are strictly necessary to provide you with services.\nThe Cookie Consent Manager can be found in the notification banner and on our website. If you choose to reject cookies, you may still use our website though your access to some functionality and areas of our website may be restricted. You may also set or amend your web browser controls to accept or refuse cookies.\n","title":"Cookie Policy","type":"general"},{"content":"This privacy notice for Ompluscator\u0026rsquo;s Blog (\u0026quot;we,\u0026quot; \u0026ldquo;us,\u0026rdquo; or \u0026ldquo;our\u0026rdquo;), describes how and why we might collect, store, use, and/or share (\u0026quot;process\u0026quot;) your information when you use our services (\u0026quot;Services\u0026quot;), such as when you:\nVisit our website at https://blog.ompluscator.io, or any website of ours that links to this privacy notice\nEngage with us in other related ways, including any sales, marketing, or events\nQuestions or concerns? # Reading this privacy notice will help you understand your privacy rights and choices. If you do not agree with our policies and practices, please do not use our Services. If you still have any questions or concerns, please contact us at marko.milojevic@ompluscator.io.\nSummary of key points # This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents_ below to find the section you are looking for._\nWhat personal information do we process? # When you visit, use, or navigate our Services, we may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use. Learn more about personal information you disclose to us.\nDo we process any sensitive personal information? # We do not process sensitive personal information.\nDo we receive any information from third parties? # We do not receive any information from third parties.\nHow do we process your information? # We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. We process your information only when we have a valid legal reason to do so. Learn more about how we process your information.\nIn what situations and with which parties do we share personal information? # We may share information in specific situations and with specific third parties. Learn more about when and with whom we share your personal information.\nHow do we keep your information safe? # We have organizational and technical processes and procedures in place to protect your personal information. However, no electronic transmission over the internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Learn more about how we keep your information safe.\nWhat are your rights? # Depending on where you are located geographically, the applicable privacy law may mean you have certain rights regarding your personal information. Learn more about your privacy rights.\nHow do you exercise your rights? # The easiest way to exercise your rights is by submitting a data subject access request, or by contacting us. We will consider and act upon any request in accordance with applicable data protection laws.\nWant to learn more about what we do with any information we collect? # Review the privacy notice in full.\nTable of contents # 1. What information do we collect?\n2. How do we process your information?\n3. What legal bases do we rely on to process your personal information?\n4. When and with whom do we share your personal information?\n5. What is our stance on third-party websites?\n6. Do we use cookies and other tracking technologies?\n7. How long do we keep your information?\n8. How do we keep your information safe?\n9. What are your privacy rights?\n10. Controls for do-not-track features\n11. Do California residents have specific privacy rights?\n12. Do Virginia residents have specific privacy rights?\n13. Do we make updates to this notice?\n14. How can you contact us about this notice?\n15. How can you review, update, or delete the data we collect from you?\n1. What information do we collect? # Personal information you disclose to us # In Short: We collect personal information that you provide to us.\nWe collect personal information that you voluntarily provide to us when you express an interest in obtaining information about us or our products and Services, when you participate in activities on the Services, or otherwise when you contact us.\nPersonal Information Provided by You. # The personal information that we collect depends on the context of your interactions with us and the Services, the choices you make, and the products and features you use. The personal information we collect may include the following:\nemail addresses Sensitive Information. # We do not process sensitive information.\nAll personal information that you provide to us must be true, complete, and accurate, and you must notify us of any changes to such personal information.\nInformation automatically collected # In Short:Some information — such as your Internet Protocol (IP) address and/or browser and device characteristics — is collected automatically when you visit our Services.\nWe automatically collect certain information when you visit, use, or navigate the Services. This information does not reveal your specific identity (like your name or contact information) but may include device and usage information, such as your IP address, browser and device characteristics, operating system, language preferences, referring URLs, device name, country, location, information about how and when you use our Services, and other technical information. This information is primarily needed to maintain the security and operation of our Services, and for our internal analytics and reporting purposes.\nLike many businesses, we also collect information through cookies and similar technologies. You can find out more about this in our Cookie Notice: Cookie Policy.\nThe information we collect includes:\nLog and Usage Data. Log and usage data is service-related, diagnostic, usage, and performance information our servers automatically collect when you access or use our Services and which we record in log files. Depending on how you interact with us, this log data may include your IP address, device information, browser type, and settings and information about your activity in the Services (such as the date/time stamps associated with your usage, pages and files viewed, searches, and other actions you take such as which features you use), device event information (such as system activity, error reports (sometimes called \u0026ldquo;crash dumps\u0026rdquo;), and hardware settings).\nDevice Data. We collect device data such as information about your computer, phone, tablet, or other device you use to access the Services. Depending on the device used, this device data may include information such as your IP address (or proxy server), device and application identification numbers, location, browser type, hardware model, Internet service provider and/or mobile carrier, operating system, and system configuration information.\nLocation Data. We collect location data such as information about your device\u0026rsquo;s location, which can be either precise or imprecise. How much information we collect depends on the type and settings of the device you use to access the Services. For example, we may use GPS and other technologies to collect geolocation data that tells us your current location (based on your IP address). You can opt out of allowing us to collect this information either by refusing access to the information or by disabling your Location setting on your device. However, if you choose to opt out, you may not be able to use certain aspects of the Services.\n2. How do we process your information? # In Short:We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent.\nWe process your personal information for a variety of reasons, depending on how you interact with our Services, including:\nTo deliver targeted advertising to you. We may process your information to develop and display personalized content and advertising tailored to your interests, location, and more. For more information see our Cookie Notice: Cookie Policy.\nTo identify usage trends. We may process information about how you use our Services to better understand how they are being used so we can improve them.\nTo save or protect an individual\u0026rsquo;s vital interest. We may process your information when necessary to save or protect an individual’s vital interest, such as to prevent harm.\n3. What legal bases do we rely on to process your information? # In Short: We only process your personal information when we believe it is necessary and we have a valid legal reason (i.e., legal basis) to do so under applicable law, like with your consent, to comply with laws, to provide you with services to enter into or fulfill our contractual obligations, to protect your rights, or to fulfill our legitimate business interests.\nIf you are located in the EU or UK, this section applies to you # The General Data Protection Regulation (GDPR) and UK GDPR require us to explain the valid legal bases we rely on in order to process your personal information. As such, we may rely on the following legal bases to process your personal information:\nConsent. We may process your information if you have given us permission (i.e., consent) to use your personal information for a specific purpose. You can withdraw your consent at any time. Learn more about withdrawing your consent.\nLegitimate Interests. We may process your information when we believe it is reasonably necessary to achieve our legitimate business interests and those interests do not outweigh your interests and fundamental rights and freedoms. For example, we may process your personal information for some of the purposes described in order to:\nDevelop and display personalized and relevant advertising content for our users\nAnalyze how our Services are used so we can improve them to engage and retain users\nLegal Obligations. We may process your information where we believe it is necessary for compliance with our legal obligations, such as to cooperate with a law enforcement body or regulatory agency, exercise or defend our legal rights, or disclose your information as evidence in litigation in which we are involved.\nVital Interests. We may process your information where we believe it is necessary to protect your vital interests or the vital interests of a third party, such as situations involving potential threats to the safety of any person.\nIf you are located in Canada, this section applies to you # We may process your information if you have given us specific permission (i.e., express consent) to use your personal information for a specific purpose, or in situations where your permission can be inferred (i.e., implied consent). You can withdraw your consent at any time.\nIn some exceptional cases, we may be legally permitted under applicable law to process your information without your consent, including, for example:\nIf collection is clearly in the interests of an individual and consent cannot be obtained in a timely way\nFor investigations and fraud detection and prevention\nFor business transactions provided certain conditions are met\nIf it is contained in a witness statement and the collection is necessary to assess, process, or settle an insurance claim\nFor identifying injured, ill, or deceased persons and communicating with next of kin\nIf we have reasonable grounds to believe an individual has been, is, or may be victim of financial abuse\nIf it is reasonable to expect collection and use with consent would compromise the availability or the accuracy of the information and the collection is reasonable for purposes related to investigating a breach of an agreement or a contravention of the laws of Canada or a province\nIf disclosure is required to comply with a subpoena, warrant, court order, or rules of the court relating to the production of records\nIf it was produced by an individual in the course of their employment, business, or profession and the collection is consistent with the purposes for which the information was produced\nIf the collection is solely for journalistic, artistic, or literary purposes\nIf the information is publicly available and is specified by the regulations\n4. When and with whom do we share your personal information? # In Short: We may share information in specific situations described in this section and/or with the following third parties.\nWe may need to share your personal information in the following situations:\nBusiness Transfers. We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company. 5. What is our stance on third-party websites? # In Short: We are not responsible for the safety of any information that you share with third parties that we may link to or who advertise on our Services, but are not affiliated with, our Services.\nThe Services may link to third-party websites, online services, or mobile applications and/or contain advertisements from third parties that are not affiliated with us and which may link to other websites, services, or applications. Accordingly, we do not make any guarantee regarding any such third parties, and we will not be liable for any loss or damage caused by the use of such third-party websites, services, or applications. The inclusion of a link towards a third-party website, service, or application does not imply an endorsement by us. We cannot guarantee the safety and privacy of data you provide to any third parties. Any data collected by third parties is not covered by this privacy notice. We are not responsible for the content or privacy and security practices and policies of any third parties, including other websites, services, or applications that may be linked to or from the Services. You should review the policies of such third parties and contact them directly to respond to your questions.\n6. Do we use cookies and other tracking technologies? # In Short: We may use cookies and other tracking technologies to collect and store your information.\nWe may use cookies and similar tracking technologies (like web beacons and pixels) to access or store information. Specific information about how we use such technologies and how you can refuse certain cookies is set out in our Cookie Notice: Cookie Policy.\n7. How long do we keep your information? # In Short: We keep your information for as long as necessary to fulfill the purposes outlined in this privacy notice unless otherwise required by law.\nWe will only keep your personal information for as long as it is necessary for the purposes set out in this privacy notice, unless a longer retention period is required or permitted by law (such as tax, accounting, or other legal requirements). No purpose in this notice will require us keeping your personal information for longer than 1 year.\nWhen we have no ongoing legitimate business need to process your personal information, we will either delete or anonymize such information, or, if this is not possible (for example, because your personal information has been stored in backup archives), then we will securely store your personal information and isolate it from any further processing until deletion is possible.\n8. How do we keep your information safe? # In Short: We aim to protect your personal information through a system of organizational and technical security measures.\nWe have implemented appropriate and reasonable technical and organizational security measures designed to protect the security of any personal information we process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Although we will do our best to protect your personal information, transmission of personal information to and from our Services is at your own risk. You should only access the Services within a secure environment.\n9. What are your privacy rights? # In Short: In some regions, such as the European Economic Area (EEA), United Kingdom (UK), Switzerland, and Canada, you have rights that allow you greater access to and control over your personal information. You may review, change, or terminate your account at any time.\nIn some regions (like the EEA, UK, Switzerland, and Canada), you have certain rights under applicable data protection laws. These may include the right (i) to request access and obtain a copy of your personal information, (ii) to request rectification or erasure; (iii) to restrict the processing of your personal information; (vi) if applicable, to data portability; and (vii) not to be subject to automated decision-making. In certain circumstances, you may also have the right to object to the processing of your personal information. You can make such a request by contacting us by using the contact details provided in the section \u0026ldquo;How can you contact us about this notice?\u0026rdquo; below.\nWe will consider and act upon any request in accordance with applicable data protection laws.\nIf you are located in the EEA or UK and you believe we are unlawfully processing your personal information, you also have the right to complain to your Member State data protection authority or UK data protection authority.\nIf you are located in Switzerland, you may contact the Federal Data Protection and Information Commissioner.\nWithdrawing your consent: # If we are relying on your consent to process your personal information, which may be express and/or implied consent depending on the applicable law, you have the right to withdraw your consent at any time. You can withdraw your consent at any time by contacting us by using the contact details provided in the section \u0026ldquo;How can you contact us about this notice?\u0026rdquo; below.\nHowever, please note that this will not affect the lawfulness of the processing before its withdrawal nor, when applicable law allows, will it affect the processing of your personal information conducted in reliance on lawful processing grounds other than consent.\nCookies and similar technologies: # Most Web browsers are set to accept cookies by default. If you prefer, you can usually choose to set your browser to remove cookies and to reject cookies. If you choose to remove cookies or reject cookies, this could affect certain features or services of our Services. You may also opt out of interest-based advertising by advertisers on our Services. For further information, please see our Cookie Notice: Cookie Policy.\nIf you have questions or comments about your privacy rights, you may email us at marko.milojevic@ompluscator.io.\n10. Controls for do-not-track features # Most web browsers and some mobile operating systems and mobile applications include a Do-Not-Track (\u0026ldquo;DNT\u0026rdquo;) feature or setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage no uniform technology standard for recognizing and implementing DNT signals has been finalized. As such, we do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that we must follow in the future, we will inform you about that practice in a revised version of this privacy notice.\n11. Do California residents have specific privacy rights? # In Short: Yes, if you are a resident of California, you are granted specific rights regarding access to your personal information.\nCalifornia Civil Code Section 1798.83, also known as the \u0026ldquo;Shine The Light\u0026rdquo; law, permits our users who are California residents to request and obtain from us, once a year and free of charge, information about categories of personal information (if any) we disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which we shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to us using the contact information provided below.\nIf you are under 18 years of age, reside in California, and have a registered account with Services, you have the right to request removal of unwanted data that you publicly post on the Services. To request removal of such data, please contact us using the contact information provided below and include the email address associated with your \\account and a statement that you reside in California. We will make sure the data is not publicly displayed on the Services, but please be aware that the data may not be completely or comprehensively removed from all our systems (e.g., backups, etc.).\nCCPA Privacy Notice # The California Code of Regulations defines a \u0026ldquo;resident\u0026rdquo; as:\nevery individual who is in the State of California for other than a temporary or transitory purpose and every individual who is domiciled in the State of California who is outside the State of California for a temporary or transitory purpose All other individuals are defined as \u0026ldquo;non-residents.\u0026rdquo;\nIf this definition of \u0026ldquo;resident\u0026rdquo; applies to you, we must adhere to certain rights and obligations regarding your personal information.\nWhat categories of personal information do we collect? # We have collected the following categories of personal information in the past twelve (12) months:\nCategory Examples Collected A. Identifiers Contact details, such as real name, alias, postal address, telephone or mobile contact number, unique personal identifier, online identifier, Internet Protocol address, email address, and account name YES B. Personal information categories listed in the California Customer Records statute Name, contact information, education, employment, employment history, and financial information NO C. Protected classification characteristics under California or federal law Gender and date of birth NO D. Commercial information Transaction information, purchase history, financial details, and payment information NO E. Biometric information Fingerprints and voiceprints NO F. Internet or other similar network activity Browsing history, search history, online behavior, interest data, and interactions with our and other websites, applications, systems, and advertisements YES G. Geolocation data Device location YES H. Audio, electronic, visual, thermal, olfactory, or similar information Images and audio, video or call recordings created in connection with our business activities NO I. Professional or employment-related information Business contact details in order to provide you our Services at a business level or job title, work history, and professional qualifications if you apply for a job with us NO J. Education Information Student records and directory information NO K. Inferences drawn from other personal information Inferences drawn from any of the collected personal information listed above to create a profile or summary about, for example, an individual’s preferences and characteristics NO L. Sensitive Personal Information NO We will use and retain the collected personal information as needed to provide the Services or for:\nCategory A - As long as the user subscribe to our newsletter.\nCategory F - 1 year\nCategory G - 1 year\nWe may also collect other personal information outside of these categories through instances where you interact with us in person, online, or by phone or mail in the context of:\nReceiving help through our customer support channels;\nParticipation in customer surveys or contests; and\nFacilitation in the delivery of our Services and to respond to your inquiries.\nHow do we use and share your personal information? # More information about our data collection and sharing practices can be found in this privacy notice and our Cookie Notice: Cookie Policy.\nYou may contact us by email at marko.milojevic@ompluscator.io, or by referring to the contact details at the bottom of this document.\nIf you are using an authorized agent to exercise your right to opt out we may deny a request if the authorized agent does not submit proof that they have been validly authorized to act on your behalf.\nWill your information be shared with anyone else? # We may disclose your personal information with our service providers pursuant to a written contract between us and each service provider. Each service provider is a for-profit entity that processes the information on our behalf, following the same strict privacy protection obligations mandated by the CCPA.\nWe may use your personal information for our own business purposes, such as for undertaking internal research for technological development and demonstration. This is not considered to be \u0026ldquo;selling\u0026rdquo; of your personal information.\nWe have not disclosed, sold, or shared any personal information to third parties for a business or commercial purpose in the preceding twelve (12) months. We will not sell or share personal information in the future belonging to website visitors, users, and other consumers.\nYour rights with respect to your personal data # Right to request deletion of the data — Request to delete # You can ask for the deletion of your personal information. If you ask us to delete your personal information, we will respect your request and delete your personal information, subject to certain exceptions provided by law, such as (but not limited to) the exercise by another consumer of his or her right to free speech, our compliance requirements resulting from a legal obligation, or any processing that may be required to protect against illegal activities.\nRight to be informed — Request to know # Depending on the circumstances, you have a right to know:\nwhether we collect and use your personal information;\nthe categories of personal information that we collect;\nthe purposes for which the collected personal information is used;\nwhether we sell or share personal information to third parties;\nthe categories of personal information that we sold, shared, or disclosed for a business purpose;\nthe categories of third parties to whom the personal information was sold, shared, or disclosed for a business purpose;\nthe business or commercial purpose for collecting, selling, or sharing personal information; and\nthe specific pieces of personal information we collected about you.\nIn accordance with applicable law, we are not obligated to provide or delete consumer information that is de-identified in response to a consumer request or to re-identify individual data to verify a consumer request.\nRight to Non-Discrimination for the Exercise of a Consumer’s Privacy Rights # We will not discriminate against you if you exercise your privacy rights.\nRight to Limit Use and Disclosure of Sensitive Personal Information # We do not process consumer\u0026rsquo;s sensitive personal information.\nVerification process # Upon receiving your request, we will need to verify your identity to determine you are the same person about whom we have the information in our system. These verification efforts require us to ask you to provide information so that we can match it with information you have previously provided us. For instance, depending on the type of request you submit, we may ask you to provide certain information so that we can match the information you provide with the information we already have on file, or we may contact you through a communication method (e.g., phone or email) that you have previously provided to us. We may also use other verification methods as the circumstances dictate.\nWe will only use personal information provided in your request to verify your identity or authority to make the request. To the extent possible, we will avoid requesting additional information from you for the purposes of verification. However, if we cannot verify your identity from the information already maintained by us, we may request that you provide additional information for the purposes of verifying your identity and for security or fraud-prevention purposes. We will delete such additionally provided information as soon as we finish verifying you.\nOther privacy rights\nYou may object to the processing of your personal information.\nYou may request correction of your personal data if it is incorrect or no longer relevant, or ask to restrict the processing of the information.\nYou can designate an authorized agent to make a request under the CCPA on your behalf. We may deny a request from an authorized agent that does not submit proof that they have been validly authorized to act on your behalf in accordance with the CCPA.\nYou may request to opt out from future selling or sharing of your personal information to third parties. Upon receiving an opt-out request, we will act upon the request as soon as feasibly possible, but no later than fifteen ( 15) days from the date of the request submission.\nTo exercise these rights, you can contact us by email at marko.milojevic@ompluscator.io, or by referring to the contact details at the bottom of this document. If you have a complaint about how we handle your data, we would like to hear from you.\n12. Do Virginia residents have specific privacy rights? # In Short: Yes, if you are a resident of Virginia, you may be granted specific rights regarding access to and use of your personal information.\nVirginia CDPA Privacy Notice # Under the Virginia Consumer Data Protection Act (CDPA):\n\u0026ldquo;Consumer\u0026rdquo; means a natural person who is a resident of the Commonwealth acting only in an individual or household context. It does not include a natural person acting in a commercial or employment context.\n\u0026ldquo;Personal data\u0026rdquo; means any information that is linked or reasonably linkable to an identified or identifiable natural person. \u0026ldquo;Personal data\u0026rdquo; does not include de-identified data or publicly available information.\n\u0026ldquo;Sale of personal data\u0026rdquo; means the exchange of personal data for monetary consideration.\nIf this definition \u0026ldquo;consumer\u0026rdquo; applies to you, we must adhere to certain rights and obligations regarding your personal data.\nThe information we collect, use, and disclose about you will vary depending on how you interact with us and our Services. To find out more, please visit the following links:\nPersonal data we collect\nHow we use your personal data\nWhen and with whom we share your personal data\nYour rights with respect to your personal data # Right to be informed whether or not we are processing your personal data\nRight to access your personal data\nRight to correct inaccuracies in your personal data\nRight to request deletion of your personal data\nRight to obtain a copy of the personal data you previously shared with us\nRight to opt out of the processing of your personal data if it is used for targeted advertising, the sale of personal data, or profiling in furtherance of decisions that produce legal or similarly significant effects (\u0026ldquo;profiling\u0026rdquo;)\nWe have not sold any personal data to third parties for business or commercial purposes. We will not sell personal data in the future belonging to website visitors, users, and other consumers.\nExercise your rights provided under the Virginia CDPA # More information about our data collection and sharing practices can be found in this privacy notice and our Cookie Notice: Cookie Policy.\nYou may contact us by email at marko.milojevic@ompluscator.io, by submitting a data subject access request, or by referring to the contact details at the bottom of this document.\nIf you are using an authorized agent to exercise your rights, we may deny a request if the authorized agent does not submit proof that they have been validly authorized to act on your behalf.\nVerification process # We may request that you provide additional information reasonably necessary to verify you and your consumer\u0026rsquo;s request. If you submit the request through an authorized agent, we may need to collect additional information to verify your identity before processing your request.\nUpon receiving your request, we will respond without undue delay, but in all cases, within forty-five (45) days of receipt. The response period may be extended once by forty-five (45) additional days when reasonably necessary. We will inform you of any such extension within the initial 45-day response period, together with the reason for the extension.\nRight to appeal # If we decline to take action regarding your request, we will inform you of our decision and reasoning behind it. If you wish to appeal our decision, please email us at marko.milojevic@ompluscator.io. Within sixty (60) days of receipt of an appeal, we will inform you in writing of any action taken or not taken in response to the appeal, including a written explanation of the reasons for the decisions. If your appeal if denied, you may contact the Attorney General to submit a complaint.\n13. Do we make updates to this notice? # In Short: Yes, we will update this notice as necessary to stay compliant with relevant laws.\nWe may update this privacy notice from time to time. The updated version will be indicated by an updated \u0026ldquo;Revised\u0026rdquo; date and the updated version will be effective as soon as it is accessible. If we make material changes to this privacy notice, we may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this privacy notice frequently to be informed of how we are protecting your information.\n14. How can you contact us about this notice? # If you have questions or comments about this notice, you may email us at marko.milojevic@ompluscator.io.\n15. How can you review, update, or delete the data we collect from you? # Based on the applicable laws of your country, you may have the right to request access to the personal information we collect from you, change that information, or delete it. To request to review, update, or delete your personal information, please fill out and submit a data subject access request.\nThis privacy policy was created using Termly\u0026rsquo;s Privacy Policy Generator.\n","date":"20 September 2023","externalUrl":null,"permalink":"/general/privacy-policy/","section":"Generals","summary":"This privacy notice for Ompluscator’s Blog (\"we,\" “us,” or “our”), describes how and why we might collect, store, use, and/or share (\"process\") your information when you use our services (\"Services\"), such as when you:\nVisit our website at https://blog.ompluscator.io, or any website of ours that links to this privacy notice\nEngage with us in other related ways, including any sales, marketing, or events\nQuestions or concerns? # Reading this privacy notice will help you understand your privacy rights and choices. If you do not agree with our policies and practices, please do not use our Services. If you still have any questions or concerns, please contact us at marko.milojevic@ompluscator.io.\nSummary of key points # This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents_ below to find the section you are looking for._\nWhat personal information do we process? # When you visit, use, or navigate our Services, we may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use. Learn more about personal information you disclose to us.\nDo we process any sensitive personal information? # We do not process sensitive personal information.\nDo we receive any information from third parties? # We do not receive any information from third parties.\nHow do we process your information? # We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. We process your information only when we have a valid legal reason to do so. Learn more about how we process your information.\nIn what situations and with which parties do we share personal information? # We may share information in specific situations and with specific third parties. Learn more about when and with whom we share your personal information.\n","title":"Privacy Policy","type":"general"},{"content":" Agreement to our legal terms # We are Ompluscator\u0026rsquo;s Blog (\u0026quot;Company,\u0026quot; \u0026ldquo;we,\u0026rdquo; \u0026ldquo;us,\u0026rdquo; \u0026ldquo;our\u0026rdquo;).\nWe operate , as well as any other related products and services that refer or link to these legal terms (the \u0026ldquo;Legal Terms\u0026rdquo;) (collectively, the \u0026ldquo;Services\u0026rdquo;).\nYou can contact us by email at Ompluscator\u0026rsquo;s Blog or by mail to Ompluscator\u0026rsquo;s Blog, Ompluscator\u0026rsquo;s Blog, Ompluscator\u0026rsquo;s Blog.\nThese Legal Terms constitute a legally binding agreement made between you, whether personally or on behalf of an entity (\u0026quot;you\u0026quot;), and Ompluscator\u0026rsquo;s Blog, concerning your access to and use of the Services. You agree that by accessing the Services, you have read, understood, and agreed to be bound by all of these Legal Terms.\nIF YOU DO NOT AGREE WITH ALL OF THESE LEGAL TERMS, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SERVICES AND YOU MUST DISCONTINUE USE IMMEDIATELY.\nSupplemental terms and conditions or documents that may be posted on the Services from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Legal Terms at any time and for any reason. We will alert you about any changes by updating the \u0026ldquo;Last updated\u0026rdquo; date of these Legal Terms, and you waive any right to receive specific notice of each such change. It is your responsibility to periodically review these Legal Terms to stay informed of updates. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Legal Terms by your continued use of the Services after the date such revised Legal Terms are posted.\nWe recommend that you print a copy of these Legal Terms for your records.\nTable of contents # 1. Our services\n2. Intellectual property rights\n3. User representations\n4. Prohibited activities\n5. User generated contributions\n6. Contribution license\n7. Services management\n8. Term and termination\n9. Modifications and interruptions\n10. Governing law\n11. Dispute resolution\n12. Corrections\n13. Disclaimer\n14. Limitations of liability\n15. Indemnification\n16. User data\n17. Electronic communications, transactions, and signatures\n18. Miscellaneous\n19. Contact us\n1. Our services # The information provided when using the Services is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Services from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.\n2. Intellectual property rights # Our intellectual property # We are the owner or the licensee of all intellectual property rights in our Services, including all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics in the Services (collectively, the \u0026ldquo;Content\u0026rdquo;), as well as the trademarks, service marks, and logos contained therein (the \u0026ldquo;Marks\u0026rdquo;).\nOur Content and Marks are protected by copyright and trademark laws (and various other intellectual property rights and unfair competition laws) and treaties in the United States and around the world.\nThe Content and Marks are provided in or through the Services \u0026ldquo;AS IS\u0026rdquo; for your personal, non-commercial use or internal business purpose only.\nYour use of our Services # Subject to your compliance with these Legal Terms, including the \u0026ldquo;Prohibited activities\u0026rdquo; section below, we grant you a non-exclusive, non-transferable, revocable license to:\naccess the Services; and download or print a copy of any portion of the Content to which you have properly gained access. solely for your personal, non-commercial use or internal business purpose.\nExcept as set out in this section or elsewhere in our Legal Terms, no part of the Services and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.\nIf you wish to make any use of the Services, Content, or Marks other than as set out in this section or elsewhere in our Legal Terms, please address your request to: Ompluscator\u0026rsquo;s Blog. If we ever grant you the permission to post, reproduce, or publicly display any part of our Services or Content, you must identify us as the owners or licensors of the Services, Content, or Marks and ensure that any copyright or proprietary notice appears or is visible on posting, reproducing, or displaying our Content.\nWe reserve all rights not expressly granted to you in and to the Services, Content, and Marks.\nAny breach of these Intellectual Property Rights will constitute a material breach of our Legal Terms and your right to use our Services will terminate immediately.\nYour submissions # Please review this section and the \u0026ldquo;Prohibited activities\u0026rdquo; section carefully prior to using our Services to understand the (a) rights you give us and (b) obligations you have when you post or upload any content through the Services.\nSubmissions # By directly sending us any question, comment, suggestion, idea, feedback, or other information about the Services (\u0026ldquo;Submissions\u0026rdquo;), you agree to assign to us all intellectual property rights in such Submission. You agree that we shall own this Submission and be entitled to its unrestricted use and dissemination for any lawful purpose, commercial or otherwise, without acknowledgment or compensation to you.\nYou are responsible for what you post or upload # By sending us Submissions through any part of the Services you:\nconfirm that you have read and agree with our \u0026ldquo;Prohibited activities\u0026rdquo; and will not post, send, publish, upload, or transmit through the Services any Submission that is illegal, harassing, hateful, harmful, defamatory, obscene, bullying, abusive, discriminatory, threatening to any person or group, sexually explicit, false, inaccurate, deceitful, or misleading; to the extent permissible by applicable law, waive any and all moral rights to any such Submission; warrant that any such Submission are original to you or that you have the necessary rights and licenses to submit such Submissions and that you have full authority to grant us the above-mentioned rights in relation to your Submissions; and warrant and represent that your Submissions do not constitute confidential information. You are solely responsible for your Submissions and you expressly agree to reimburse us for any and all losses that we may suffer because of your breach of (a) this section, (b) any third party’s intellectual property rights, or (c) applicable law.\n3. User representations # By using the Services, you represent and warrant that: (1) you have the legal capacity and you agree to comply with these Legal Terms; (2) you are not a minor in the jurisdiction in which you reside; (3) you will not access the Services through automated or non-human means, whether through a bot, script or otherwise; (4) you will not use the Services for any illegal or unauthorized purpose; and (5) your use of the Services will not violate any applicable law or regulation.\nIf you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Services (or any portion thereof).\n4. Prohibited activities # You may not access or use the Services for any purpose other than that for which we make the Services available. The Services may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us.\nAs a user of the Services, you agree not to:\nSystematically retrieve data or other content from the Services to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us. Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords. Circumvent, disable, or otherwise interfere with security-related features of the Services, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Services and/or the Content contained therein. Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Services. Use any information obtained from the Services in order to harass, abuse, or harm another person. Make improper use of our support services or submit false reports of abuse or misconduct. Use the Services in a manner inconsistent with any applicable laws or regulations. Engage in unauthorized framing of or linking to the Services. Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Services or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Services. Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools. Delete the copyright or other proprietary rights notice from any Content. Attempt to impersonate another user or person or use the username of another user. Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats (\u0026ldquo;gifs\u0026rdquo;), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as \u0026ldquo;spyware\u0026rdquo; or \u0026ldquo;passive collection mechanisms\u0026rdquo; or \u0026ldquo;pcms\u0026rdquo;). Interfere with, disrupt, or create an undue burden on the Services or the networks or services connected to the Services. Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Services to you. Attempt to bypass any measures of the Services designed to prevent or restrict access to the Services, or any portion of the Services. Copy or adapt the Services\u0026rsquo; software, including but not limited to Flash, PHP, HTML, JavaScript, or other code. Except as permitted by applicable law, decipher, decompile, disassemble, or reverse engineer any of the software comprising or in any way making up a part of the Services. Except as may be the result of standard search engine or Internet browser usage, use, launch, develop, or distribute any automated system, including without limitation, any spider, robot, cheat utility, scraper, or offline reader that accesses the Services, or use or launch any unauthorized script or other software. Use a buying agent or purchasing agent to make purchases on the Services. Make any unauthorized use of the Services, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses. Use the Services as part of any effort to compete with us or otherwise use the Services and/or the Content for any revenue-generating endeavor or commercial enterprise. 5. User generated contributions # The Services do not offer users to submit or post content. We may provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content and materials to us or on the Services, including but not limited to text, writings, video, audio, photographs, graphics, comments, suggestions, or personal information or other material (collectively, \u0026ldquo;Contributions\u0026rdquo;). Contributions may be viewable by other users of the Services and through third-party websites. When you create or make available any Contributions, you thereby represent and warrant that:\n6. Contribution license # You and Services agree that we may access, store, process, and use any information and personal data that you provide and your choices (including settings).\nBy submitting suggestions or other feedback regarding the Services, you agree that we can use and share such feedback for any purpose without compensation to you.\nWe do not assert any ownership over your Contributions. You retain full ownership of all of your Contributions and any intellectual property rights or other proprietary rights associated with your Contributions. We are not liable for any statements or representations in your Contributions provided by you in any area on the Services. You are solely responsible for your Contributions to the Services and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.\n7. Services management # We reserve the right, but not the obligation, to: (1) monitor the Services for violations of these Legal Terms; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Legal Terms, including without limitation, reporting such user to law enforcement authorities; (3) in our sole discretion and without limitation, refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions or any portion thereof; (4) in our sole discretion and without limitation, notice, or liability, to remove from the Services or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems; and (5) otherwise manage the Services in a manner designed to protect our rights and property and to facilitate the proper functioning of the Services.\n8. Term and termination # These Legal Terms shall remain in full force and effect while you use the Services.\nWITHOUT LIMITING ANY OTHER PROVISION OF THESE LEGAL TERMS, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE SERVICES (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE LEGAL TERMS OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE SERVICES OR DELETE ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.\nIf we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.\n9. Modifications and interruptions # We reserve the right to change, modify, or remove the contents of the Services at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Services. We will not be liable to you or any third party for any modification, price change, suspension, or discontinuance of the Services.\nWe cannot guarantee the Services will be available at all times. We may experience hardware, software, or other problems or need to perform maintenance related to the Services, resulting in interruptions, delays, or errors. We reserve the right to change, revise, update, suspend, discontinue, or otherwise modify the Services at any time or for any reason without notice to you. You agree that we have no liability whatsoever for any loss, damage, or inconvenience caused by your inability to access or use the Services during any downtime or discontinuance of the Services. Nothing in these Legal Terms will be construed to obligate us to maintain and support the Services or to supply any corrections, updates, or releases in connection therewith.\n10. Governing law # These Legal Terms shall be governed by and defined following the laws of Ompluscator\u0026rsquo;s Blog. Ompluscator\u0026rsquo;s Blog and yourself irrevocably consent that the courts of Ompluscator\u0026rsquo;s Blog shall have exclusive jurisdiction to resolve any dispute which may arise in connection with these Legal Terms.\n11. Dispute resolution** # Informal Negotiations # To expedite resolution and control the cost of any dispute, controversy, or claim related to these Legal Terms (each a \u0026ldquo;Dispute\u0026rdquo; and collectively, the \u0026ldquo;Disputes\u0026rdquo;) brought by either you or us (individually, a \u0026ldquo;Party\u0026rdquo; and collectively, the \u0026ldquo;Parties\u0026rdquo;), the Parties agree to first attempt to negotiate any Dispute (except those Disputes expressly provided below) informally for at least Ompluscator\u0026rsquo;s Blog days before initiating arbitration. Such informal negotiations commence upon written notice from one Party to the other Party.\nBinding Arbitration # Any dispute arising out of or in connection with these Legal Terms, including any question regarding its existence, validity, or termination, shall be referred to and finally resolved by the International Commercial Arbitration Court under the European Arbitration Chamber (Belgium, Brussels, Avenue Louise, 146) according to the Rules of this ICAC, which, as a result of referring to it, is considered as the part of this clause. The number of arbitrators shall be Ompluscator\u0026rsquo;s Blog. The seat, or legal place, or arbitration shall be Ompluscator\u0026rsquo;s Blog. The language of the proceedings shall be Ompluscator\u0026rsquo;s Blog. The governing law of these Legal Terms shall be substantive law of Ompluscator\u0026rsquo;s Blog.\nRestrictions # The Parties agree that any arbitration shall be limited to the Dispute between the Parties individually. To the full extent permitted by law, (a) no arbitration shall be joined with any other proceeding; (b) there is no right or authority for any Dispute to be arbitrated on a class-action basis or to utilize class action procedures; and (c) there is no right or authority for any Dispute to be brought in a purported representative capacity on behalf of the general public or any other persons.\nExceptions to Informal Negotiations and Arbitration # The Parties agree that the following Disputes are not subject to the above provisions concerning informal negotiations binding arbitration: (a) any Disputes seeking to enforce or protect, or concerning the validity of, any of the intellectual property rights of a Party; (b) any Dispute related to, or arising from, allegations of theft, piracy, invasion of privacy, or unauthorized use; and (c) any claim for injunctive relief. If this provision is found to be illegal or unenforceable, then neither Party will elect to arbitrate any Dispute falling within that portion of this provision found to be illegal or unenforceable and such Dispute shall be decided by a court of competent jurisdiction within the courts listed for jurisdiction above, and the Parties agree to submit to the personal jurisdiction of that court.\n12. Corrections # There may be information on the Services that contains typographical errors, inaccuracies, or omissions, including descriptions, pricing, availability, and various other information. We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Services at any time, without prior notice.\n13. Disclaimer # THE SERVICES ARE PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS. YOU AGREE THAT YOUR USE OF THE SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE SERVICES AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE SERVICES\u0026rsquo; CONTENT OR THE CONTENT OF ANY WEBSITES OR MOBILE APPLICATIONS LINKED TO THE SERVICES AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE SERVICES, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE SERVICES, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE SERVICES BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE SERVICES. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE SERVICES, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.\n14. Limitations of liability # IN NO EVENT WILL WE OR OUR DIRECTORS, EMPLOYEES, OR AGENTS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, SPECIAL, OR PUNITIVE DAMAGES, INCLUDING LOST PROFIT, LOST REVENUE, LOSS OF DATA, OR OTHER DAMAGES ARISING FROM YOUR USE OF THE SERVICES, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR ANY CAUSE WHATSOEVER AND REGARDLESS OF THE FORM OF THE ACTION, WILL AT ALL TIMES BE LIMITED TO THE LESSER OF THE AMOUNT PAID, IF ANY, BY YOU TO US OR . CERTAIN US STATE LAWS AND INTERNATIONAL LAWS DO NOT ALLOW LIMITATIONS ON IMPLIED WARRANTIES OR THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES. IF THESE LAWS APPLY TO YOU, SOME OR ALL OF THE ABOVE DisclaimerS OR LIMITATIONS MAY NOT APPLY TO YOU, AND YOU MAY HAVE ADDITIONAL RIGHTS.\n15. Indemnification # You agree to defend, indemnify, and hold us harmless, including our subsidiaries, affiliates, and all of our respective officers, agents, partners, and employees, from and against any loss, damage, liability, claim, or demand, including reasonable attorneys’ fees and expenses, made by any third party due to or arising out of: (1) use of the Services; (2) breach of these Legal Terms; (3) any breach of your representations and warranties set forth in these Legal Terms; (4) your violation of the rights of a third party, including but not limited to intellectual property rights; or (5) any overt harmful act toward any other user of the Services with whom you connected via the Services. Notwithstanding the foregoing, we reserve the right, at your expense, to assume the exclusive defense and control of any matter for which you are required to indemnify us, and you agree to cooperate, at your expense, with our defense of such claims. We will use reasonable efforts to notify you of any such claim, action, or proceeding which is subject to this indemnification upon becoming aware of it.\n16. User data # We will maintain certain data that you transmit to the Services for the purpose of managing the performance of the Services, as well as data relating to your use of the Services. Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Services. You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.\n17. Electronic communications, transactions, and signatures # Visiting the Services, sending us emails, and completing online forms constitute electronic communications. You consent to receive electronic communications, and you agree that all agreements, notices, disclosures, and other communications we provide to you electronically, via email and on the Services, satisfy any legal requirement that such communication be in writing.\nYOU HEREBY AGREE TO THE USE OF ELECTRONIC SIGNATURES, CONTRACTS, ORDERS, AND OTHER RECORDS, AND TO ELECTRONIC DELIVERY OF NOTICES, POLICIES, AND RECORDS OF TRANSACTIONS INITIATED OR COMPLETED BY US OR VIA THE SERVICES.\nYou hereby waive any rights or requirements under any statutes, regulations, rules, ordinances, or other laws in any jurisdiction which require an original signature or delivery or retention of non-electronic records, or to payments or the granting of credits by any means other than electronic means.\n18. Miscellaneous # These Legal Terms and any policies or operating rules posted by us on the Services or in respect to the Services constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Legal Terms shall not operate as a waiver of such right or provision. These Legal Terms operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control. If any provision or part of a provision of these Legal Terms is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Legal Terms and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Legal Terms or use of the Services. You agree that these Legal Terms will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Legal Terms and the lack of signing by the parties hereto to execute these Legal Terms.\n19. Contact us # In order to resolve a complaint regarding the Services or to receive further information regarding use of the Services, please contact us at marko.milojevic@ompluscator.io:\nThese terms of use were created using Termly\u0026rsquo;s Terms and Conditions Generator.\n","date":"20 September 2023","externalUrl":null,"permalink":"/general/terms-and-conditions/","section":"Generals","summary":"Agreement to our legal terms # We are Ompluscator’s Blog (\"Company,\" “we,” “us,” “our”).\nWe operate , as well as any other related products and services that refer or link to these legal terms (the “Legal Terms”) (collectively, the “Services”).\nYou can contact us by email at Ompluscator’s Blog or by mail to Ompluscator’s Blog, Ompluscator’s Blog, Ompluscator’s Blog.\nThese Legal Terms constitute a legally binding agreement made between you, whether personally or on behalf of an entity (\"you\"), and Ompluscator’s Blog, concerning your access to and use of the Services. You agree that by accessing the Services, you have read, understood, and agreed to be bound by all of these Legal Terms.\nIF YOU DO NOT AGREE WITH ALL OF THESE LEGAL TERMS, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SERVICES AND YOU MUST DISCONTINUE USE IMMEDIATELY.\nSupplemental terms and conditions or documents that may be posted on the Services from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Legal Terms at any time and for any reason. We will alert you about any changes by updating the “Last updated” date of these Legal Terms, and you waive any right to receive specific notice of each such change. It is your responsibility to periodically review these Legal Terms to stay informed of updates. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Legal Terms by your continued use of the Services after the date such revised Legal Terms are posted.\nWe recommend that you print a copy of these Legal Terms for your records.\nTable of contents # 1. Our services\n2. Intellectual property rights\n3. User representations\n4. Prohibited activities\n5. User generated contributions\n6. Contribution license\n7. Services management\n8. Term and termination\n9. Modifications and interruptions\n10. Governing law\n11. Dispute resolution\n12. Corrections\n13. Disclaimer\n14. Limitations of liability\n15. Indemnification\n16. User data\n17. Electronic communications, transactions, and signatures\n18. Miscellaneous\n19. Contact us\n1. Our services # The information provided when using the Services is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Services from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.\n","title":"Terms and Conditions","type":"general"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/tags/generics/","section":"Tags","summary":"","title":"Generics","type":"tags"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/series/generics-in-golang/","section":"Series","summary":"","title":"Generics in Golang","type":"series"},{"content":"After months and years of talking, trying things out, and testing, we\u0026rsquo;ve finally reached a big moment in our favorite programming language. The new Golang version, 1.18, is here. We knew it would bring significant changes to Go\u0026rsquo;s codebase, even before Generics was officially released. For a long time, when we wanted to make our code more general and abstract, we used code generators in Go. Learning what the \u0026ldquo;Go way\u0026rdquo; of doing things was challenging for many of us, but it also led to many breakthroughs. It was worth the effort. Now, there are new possibilities on the horizon.\nMany new packages have emerged, giving us ideas on how we can improve the Go ecosystem with reusable code that makes life easier for all of us. This inspiration led me to create a small proof of concept using the Gorm library. Now, let\u0026rsquo;s take a look at it.\nSource code # When I wrote this article, it relied on a GitHub Repository. The code served as a Go library proof of concept, with my intention to continue working on it. However, it was not yet suitable for production use, and I had no plans to offer production support at that time.\nYou can find the current features by following the link, and below, there is a smaller sample snippet.\nExample Usage\npackage main import ( \u0026#34;github.com/ompluscator/gorm-generics\u0026#34; // some imports ) // Product is a domain entity type Product struct { // some fields } // ProductGorm is DTO used to map Product entity to database type ProductGorm struct { // some fields } // ToEntity respects the gorm_generics.GormModel interface func (g ProductGorm) ToEntity() Product { return Product{ // some fields } } // FromEntity respects the gorm_generics.GormModel interface func (g ProductGorm) FromEntity(product Product) interface{} { return ProductGorm{ // some fields } } func main() { db, err := gorm.Open(/* DB connection string */) // handle error err = db.AutoMigrate(ProductGorm{}) // handle error // initialize a new Repository with by providing // GORM model and Entity as type repository := gorm_generics.NewRepository[ProductGorm, Product](db) ctx := context.Background() // create new Entity product := Product{ // some fields } // send new Entity to Repository for storing err = repository.Insert(ctx, \u0026amp;product) // handle error fmt.Println(product) // Out: // {1 product1 100 true} single, err := repository.FindByID(ctx, product.ID) // handle error fmt.Println(single) // Out: // {1 product1 100 true} } Why have I picked ORM for PoC? # Coming from a background in software development with traditional object-oriented programming languages like Java, C#, and PHP, one of the first things I did was search Google for a suitable ORM for Golang. Please forgive my inexperience at the time, but that\u0026rsquo;s what I was expecting. It\u0026rsquo;s not that I can\u0026rsquo;t work without an ORM. It\u0026rsquo;s just that I don\u0026rsquo;t particularly like how raw MySQL queries appear in the code. All that string concatenation looks messy to me. On the other hand, I always prefer to dive right into writing business logic, with minimal time spent thinking about the underlying data storage. Sometimes, during the implementation, I change my mind and switch to different types of storage. That\u0026rsquo;s where ORMs come in handy.\nIn summary, ORM provides me with:\nCleaner code. More flexibility in choosing the type of underlying data storage. The ability to focus entirely on business logic rather than technical details. There are many ORM [solutions](https://github.com/d-tsuji/awesome-go-orms solutions) available for Golang, and I\u0026rsquo;ve used most of them. Not surprisingly, I\u0026rsquo;ve used GORM the most because it covers a wide range of features. Yes, it lacks some well-known patterns like Identity Map, Unit of Work, and Lazy Load, but I can work without them. However, I have often missed the Repository pattern because I\u0026rsquo;ve encountered duplicated or very similar code blocks from time to time (and I really dislike repeating myself).\nFor that purpose, I sometimes used the GNORM library, which had templating logic that allowed me to create Repository structures with freedom. While I liked the idea that GNORM presented (very much in line with The Golang Way!), constantly updating templates to add new features to the Repository didn\u0026rsquo;t look good. I attempted to provide my own implementation that relied on reflection and share it with the Open Source community. Unfortunately, it didn\u0026rsquo;t go as planned. It worked, but maintaining the library was painful, and its performance was not exceptional. In the end, I deleted the GitHub repository. And just as I was giving up on this ORM upgrade in Go, Generics came into play. Oh, boy. Oh, boy! I was back to the drawing board immediately.\nImplementation # Part of my background involves Domain-Driven Design. This means I prefer to separate the domain layer from the infrastructure layer. Some ORMs treat the Entity pattern more like a Row Data Gateway or Active Record. However, because its name references the DDD pattern Entity, we can sometimes get confused and end up mixing business logic and technical details in the same class, creating a kind of monster.\nThe Entity pattern isn\u0026rsquo;t related to mapping to a database table schema or the underlying storage in any way. So, I always use Entity in the domain layer and Data Access Objects (DAO in the infrastructure layer. The signature of my Repositories always supports only Entity, but internally, they use DTO to map data to and from a database and fetch and store them into Entity. This approach guarantees a functional Anti-Corruption Layer.\nIn this case, I work with a trio of interfaces and structures, as you can see in the diagram below:\nEntity, which holds business logic in the domain layer. GormModel, serving as a DAO used to map data from Entity into a database. GormRepository, functioning as an orchestrator for querying and persisting data. Gorm Generics Two main parts, GormModel and GormRepository, require generic types to define the signatures of their methods. Utilizing generics enables us to specify GormRepository as a struct and create a more generalized implementation:\nGormRepository methods\nfunc (r *GormRepository[M, E]) Insert(ctx context.Context, entity *E) error { // map the data from Entity to DTO var start M model := start.FromEntity(*entity).(M) // create new record in the database err := r.db.WithContext(ctx).Create(\u0026amp;model).Error // handle error // map fresh record\u0026#39;s data into Entity *entity = model.ToEntity() return nil } func (r *GormRepository[M, E]) FindByID(ctx context.Context, id uint) (E, error) { // retrieve a record by id from a database var model M err := r.db.WithContext(ctx).First(\u0026amp;model, id).Error // handle error // map data into Entity return model.ToEntity(), nil } func (r *GormRepository[M, E]) Find(ctx context.Context, specification Specification) ([]E, error) { // retreive reords by some criteria var models []M err := r.db.WithContext(ctx).Where(specification.GetQuery(), specification.GetValues()...).Find(\u0026amp;models).Error // handle error // mapp all records into Entities result := make([]E, 0, len(models)) for _, row := range models { result = append(result, row.ToEntity()) } return result, nil } I didn\u0026rsquo;t intend to add more or less complex features like preloading, joins, or even limit and offset for this proof of concept. The idea was to test the simplicity of implementing generics in Go with the GORM library. In the code snippet, you can see that the GormRepository struct supports inserting new records, retrieving records by identity, and querying by Specification.\nThe Specification pattern is another pattern from Domain-Driven Design that we can use for various purposes, including querying data from storage. The proof of concept provided here defines a Specification interface, which provides a WHERE clause and the values used inside it. This does require some usage of generics for comparable operators and could potentially serve as a precursor for a future Query Object:\nSpecification example\ntype Specification interface { GetQuery() string GetValues() []any } // joinSpecification is the real implementation of Specification interface. // It is used fo AND and OR operators. type joinSpecification struct { specifications []Specification separator string } // GetQuery concats all subqueries func (s joinSpecification) GetQuery() string { queries := make([]string, 0, len(s.specifications)) for _, spec := range s.specifications { queries = append(queries, spec.GetQuery()) } return strings.Join(queries, fmt.Sprintf(\u0026#34; %s \u0026#34;, s.separator)) } // GetQuery concats all subvalues func (s joinSpecification) GetValues() []any { values := make([]any, 0) for _, spec := range s.specifications { values = append(values, spec.GetValues()...) } return values } // And delivers AND operator as Specification func And(specifications ...Specification) Specification { return joinSpecification{ specifications: specifications, separator: \u0026#34;AND\u0026#34;, } } // notSpecification negates sub-Specification type notSpecification struct { Specification } // GetQuery negates subquery func (s notSpecification) GetQuery() string { return fmt.Sprintf(\u0026#34; NOT (%s)\u0026#34;, s.Specification.GetQuery()) } // Not delivers NOT operator as Specification func Not(specification Specification) Specification { return notSpecification{ specification, } } // binaryOperatorSpecification defines binary operator as Specification // It is used for =, \u0026gt;, \u0026lt;, \u0026gt;=, \u0026lt;= operators. type binaryOperatorSpecification[T any] struct { field string operator string value T } // GetQuery builds query for binary operator func (s binaryOperatorSpecification[T]) GetQuery() string { return fmt.Sprintf(\u0026#34;%s %s ?\u0026#34;, s.field, s.operator) } // GetValues returns a value for binary operator func (s binaryOperatorSpecification[T]) GetValues() []any { return []any{s.value} } // Not delivers = operator as Specification func Equal[T any](field string, value T) Specification { return binaryOperatorSpecification[T]{ field: field, operator: \u0026#34;=\u0026#34;, value: value, } } The Specification part of the package offers the ability to provide custom criteria to the Repository and fetch data that meets those criteria. It allows for combining, negating, and extending criteria as needed.\nResults # This implementation ultimately achieves the main objective of this proof of concept, which is to create a generalized interface for querying records from the database.\nOutcome\nerr := repository.Insert(ctx, \u0026amp;Product{ Name: \u0026#34;product2\u0026#34;, Weight: 50, IsAvailable: true, }) // error handling err = repository.Insert(ctx, \u0026amp;Product{ Name: \u0026#34;product3\u0026#34;, Weight: 250, IsAvailable: false, }) // error handling many, err := repository.Find(ctx, gorm_generics.And( gorm_generics.GreaterOrEqual(\u0026#34;weight\u0026#34;, 90), gorm_generics.Equal(\u0026#34;is_available\u0026#34;, true)), ) // error handling fmt.Println(many) // Out: // [{1 product1 100 true}] Concerning my aspirations, the code snippet from above delivers a quick and elegant way to retrieve data in a clean and readable form. And without affecting performance (significantly).\nConclusion # Exploring generics for the first time following the official release of Go 1.18 was quite refreshing. I\u0026rsquo;ve been facing some challenges lately, and having this opportunity for new ideas was just what I needed. Additionally, resuming my blogging after a long break was something I felt compelled to do. It\u0026rsquo;s wonderful to share my opinions publicly once more, and I\u0026rsquo;m eagerly anticipating all the feedback you folks can provide.\nUseful Resources # Go 1.18 Release Notes GitHub Repository Gorm Martin Fowler ","date":"20 September 2023","externalUrl":null,"permalink":"/article/golang/tutorial-generics-gorm/","section":"Articles","summary":"After months and years of talking, trying things out, and testing, we’ve finally reached a big moment in our favorite programming language. The new Golang version, 1.18, is here. We knew it would bring significant changes to Go’s codebase, even before Generics was officially released. For a long time, when we wanted to make our code more general and abstract, we used code generators in Go. Learning what the “Go way” of doing things was challenging for many of us, but it also led to many breakthroughs. It was worth the effort. Now, there are new possibilities on the horizon.\nMany new packages have emerged, giving us ideas on how we can improve the Go ecosystem with reusable code that makes life easier for all of us. This inspiration led me to create a small proof of concept using the Gorm library. Now, let’s take a look at it.\nSource code # When I wrote this article, it relied on a GitHub Repository. The code served as a Go library proof of concept, with my intention to continue working on it. However, it was not yet suitable for production use, and I had no plans to offer production support at that time.\nYou can find the current features by following the link, and below, there is a smaller sample snippet.\nExample Usage\npackage main import ( \"github.com/ompluscator/gorm-generics\" // some imports ) // Product is a domain entity type Product struct { // some fields } // ProductGorm is DTO used to map Product entity to database type ProductGorm struct { // some fields } // ToEntity respects the gorm_generics.GormModel interface func (g ProductGorm) ToEntity() Product { return Product{ // some fields } } // FromEntity respects the gorm_generics.GormModel interface func (g ProductGorm) FromEntity(product Product) interface{} { return ProductGorm{ // some fields } } func main() { db, err := gorm.Open(/* DB connection string */) // handle error err = db.AutoMigrate(ProductGorm{}) // handle error // initialize a new Repository with by providing // GORM model and Entity as type repository := gorm_generics.NewRepository[ProductGorm, Product](db) ctx := context.Background() // create new Entity product := Product{ // some fields } // send new Entity to Repository for storing err = repository.Insert(ctx, \u0026product) // handle error fmt.Println(product) // Out: // {1 product1 100 true} single, err := repository.FindByID(ctx, product.ID) // handle error fmt.Println(single) // Out: // {1 product1 100 true} } Why have I picked ORM for PoC? # Coming from a background in software development with traditional object-oriented programming languages like Java, C#, and PHP, one of the first things I did was search Google for a suitable ORM for Golang. Please forgive my inexperience at the time, but that’s what I was expecting. It’s not that I can’t work without an ORM. It’s just that I don’t particularly like how raw MySQL queries appear in the code. All that string concatenation looks messy to me. On the other hand, I always prefer to dive right into writing business logic, with minimal time spent thinking about the underlying data storage. Sometimes, during the implementation, I change my mind and switch to different types of storage. That’s where ORMs come in handy.\n","title":"Golang Tutorial: Generics with Gorm","type":"article"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/tags/gorm/","section":"Tags","summary":"","title":"Gorm","type":"tags"},{"content":"","date":"20 September 2023","externalUrl":null,"permalink":"/tags/orm/","section":"Tags","summary":"","title":"Orm","type":"tags"},{"content":"Unit testing has always been my thing, almost like a hobby. There was a time when I was obsessed with it, and I made sure that all my projects had at least 90% unit test coverage. You can probably imagine how much time it can take to make such a significant change in the codebase. However, the result was worth it because I rarely encountered bugs related to business logic. Most of the issues were related to integration problems with other services or databases.\nAdding new business rules was a breeze because there were already tests in place to cover all the cases from before. The key was to ensure that these tests remained successful in the end. Sometimes, I didn\u0026rsquo;t even need to check the entire running service; having the new and old unit tests pass was sufficient.\nOnce, while working on a personal project, I had to write unit tests to cover numerous Go structs and functions—more than 100 in total. It consumed my entire weekend, and late on a Sunday night, before heading out on a business trip the next day, I set an alarm clock to wake me up. I had hardly slept that night; it was one of those restless nights when you dream but are also aware of yourself and your surroundings. My brain was active the entire time, and in my dreams, I kept writing unit tests for my alarm clock. To my surprise, each time I executed a unit test in my dream, the alarm rang. It continued ringing throughout the night.\nAnd yes, I almost forgot to mention, for two years, we had zero bugs in production. The application continued to fetch all the data and send all the emails every Monday. I don\u0026rsquo;t even remember my Gitlab password anymore.\nUnit Testing and Mocking (in general) # In Martin Fowler\u0026rsquo;s article, we can identify two types of unit tests:\nSociable unit tests, where we test a unit while it relies on other objects in conjunction with it. For example, if we want to test the UserController, we would test it along with the UserRepository, which communicates with the database.\nSolitary unit tests, where we test a unit in complete isolation. In this scenario, we would test the UserController, which interacts with a controlled, mocked UserRepository. With mocking, we can specify how it behaves without involving a database.\nBoth approaches are valid and have their place in a project, and I personally use both of them. When writing sociable unit tests, the process is straightforward; I utilize the components already present in my module and test their logic together. However, when it comes to mocking in Go, it\u0026rsquo;s not a standard procedure. Go doesn\u0026rsquo;t support inheritance but relies on composition. This means one struct doesn\u0026rsquo;t extend another but contains it. Consequently, Go doesn\u0026rsquo;t support polymorphism at the struct level but instead relies on interfaces. So, when your struct depends directly on another struct instance or when a function expects a specific struct as an argument, mocking that struct can be challenging.\nIn the code example below, we have a simple case with UserDBRepository and AdminController. AdminController directly depends on an instance of UserDBRepository, which is the implementation of the Repository responsible for communicating with the database.\nUserDBRepository struct\ntype UserDBRepository struct { connection *sql.DB } func (r *UserDBRepository) FilterByLastname(ctx context.Context, lastname string) ([]User, error) { var users []User // // do something with users // return users, nil } AdminController struct\ntype AdminController struct { repository *UserDBRepository } func (c *AdminController) FilterByLastname(ctx *gin.Context) { lastname := ctx.Param(\u0026#34;name\u0026#34;) c.repository.FilterByLastname(ctx, lastname) // // do something with users // } When it comes to writing unit tests for the AdminController to check if it generates the correct JSON response, we have two options:\nProvide a fresh instance of UserDBRepository along with a database connection to AdminController and hope that it will be the only dependency you need to pass over time.\nDon\u0026rsquo;t provide anything and expect a nil pointer exception as soon as you start running the test.\nTo avoid the latter case and to enable proper unit testing, we need to ensure that our code adheres to the Dependency Inversion Principle. Once we this principle, our refactored code takes on the shape shown in the example below. In this improved structure, the actual AdminController depends on the UserRepository interface, without specifying whether it\u0026rsquo;s a repository for a database or something else.\nUserRepository interface\ntype UserRepository interface { GetByID(ctx context.Context, ID string) (*User, error) GetByEmail(ctx context.Context, email string) (*User, error) FilterByLastname(ctx context.Context, lastname string) ([]User, error) Create(ctx context.Context, user User) (*User, error) Update(ctx context.Context, user User) (*User, error) Delete(ctx context.Context, user User) (*User, error) } AdminController struct\ntype AdminController struct { repository UserRepository } func NewAdminController(repository UserRepository) *AdminController { return \u0026amp;AdminController{ repository: repository, } } func (c *AdminController) FilterByLastname(ctx *gin.Context) { lastname := ctx.Param(\u0026#34;name\u0026#34;) c.repository.FilterByLastname(ctx, lastname) // // do something with users // } Now that we have a starting point, let\u0026rsquo;s explore how we can perform mocking most effectively.\nGenerate Mocks # There are several libraries for generating mocks, and you can even create your own generator if you prefer. Personally, I like the Mockery package. It provides mocks that are supported by the Testify package, which is a good enough reason to stick with it.\nLet\u0026rsquo;s revisit the previous example with UserRepository and AdminController. AdminController expects the UserRepository interface to filter Users by their Lastname whenever somebody sends a request to the /users endpoint. Strictly speaking, AdminController doesn\u0026rsquo;t care about how the UserRepository finds the result. Depending on whether it receives a slice of Users or an error, the crucial part is to attach the appropriate response to the Context from the Gin package.\nApplication code\nfunc main() { var repository UserRepository // // initialize repository // controller := NewAdminController(repository) router := gin.Default() router.GET(\u0026#34;/users/:lastname\u0026#34;, controller.FilterByLastname) // // do something with router // } In this example, I have used the Gin package for routing, but it doesn\u0026rsquo;t matter which package we want to use for that purpose. We would first initialize the actual implementation of UserRepository, pass it to AdminController, and define endpoints before running our server.\nAt this point, our project structure may look like this:\nProject structure\nuser-service ├── cmd │ └── main.go └── pkg └── user ├── user.go ├── admin_controller.go └── admin_controller_test.go Now, inside the user folder, we can execute the Mockery command to generate mock objects.\nMockery command\n$ mockery --all --case=underscore The content of the generated file looks like the example below:\nGenerated file\n// Code generated by mockery v1.0.0. DO NOT EDIT. package mocks import ( // // some imports // mock \u0026#34;github.com/stretchr/testify/mock\u0026#34; ) // UserRepository is an autogenerated mock type for the UserRepository type type UserRepository struct { mock.Mock } // Create provides a mock function with given fields: ctx, _a1 func (_m *UserRepository) Create(ctx context.Context, _a1 user.User) (*user.User, error) { ret := _m.Called(ctx, _a1) var r0 *user.User if rf, ok := ret.Get(0).(func(context.Context, user.User) *user.User); ok { r0 = rf(ctx, _a1) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*user.User) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, user.User) error); ok { r1 = rf(ctx, _a1) } else { r1 = ret.Error(1) } return r0, r1 } // and so on.... When I work on a project, I like to have all commands written somewhere inside the project. Sometimes, it can be a Makefile or a bash script. But here, we can add an additional generate.go file inside the user folder and place the following code inside of it:\nFile /pkg/user/generate.go\npackage user //go:generate go run github.com/vektra/mockery/cmd/mockery -all -case=underscore New Project structure\nuser-service ├── cmd │ └── main.go └── pkg └── user ├── mocks │ └── user_repository.go ├── user.go ├── admin_controller.go ├── admin_controller_test.go └── generate.go This file contains a specific comment, starting with go:generate. It includes a flag for executing the code after it, and as soon as you run the command from below inside the project\u0026rsquo;s root folder, it will generate all files:\nGenerate command\n$ go generate ./... Both approaches ultimately yield the same result — a generated file with a mocked object. So, writing solitary unit tests should no longer be an issue:\nFile /pkg/user/admin_controller_test.go\nfunc TestAdminController(t *testing.T) { var ctx *gin.Context // // setup context // repository := \u0026amp;mocks.UserRepository{} repository. On(\u0026#34;FilterByLastname\u0026#34;, ctx, \u0026#34;some last name\u0026#34;). Return(nil, errors.New(\u0026#34;some error\u0026#34;)). Once() controller := NewAdminController(repository) controller.FilterByLastname(ctx) // // do some checking for ctx // } Partial mocking of Interface # Sometimes, there is no need to mock all the methods from the interface, or the package is not under our control, preventing us from generating files. It also doesn\u0026rsquo;t make sense to create and maintain files in our library. However, there are instances when an interface contains numerous methods, and we only need a subset of them. In such cases, we can use an example with UserRepository. AdminController utilizes only one function from the Repository, which is FilterByLastname. This means we don\u0026rsquo;t require any other methods to test AdminController. To address this, let\u0026rsquo;s create a struct called MockedUserRepository, as shown in the example below:\nMockedUserRepository struct\ntype MockedUserRepository struct { UserRepository filterByLastnameFunc func(ctx context.Context, lastname string) ([]User, error) } func (r *MockedUserRepository) FilterByLastname(ctx context.Context, lastname string) ([]User, error) { return r.filterByLastnameFunc(ctx, lastname) } MockedUserRepository implements the UserRepository interface. We ensured this by embedding the UserRepository interface inside MockedUserRepository. Our mock object expects to contain an instance of the UserRepository interface within it. If that instance is not defined, it will default to nil. Additionally, it has one field, which is a function type with the same signature as FilterByLastname. The FilterByLastname method is attached to the mocked struct, and it simply forwards calls to this private field. Now, if we rewrite our test as follows, it may appear more intuitive:\nFile /pkg/user/admin_controller_test.go\nfunc TestAdminController(t *testing.T) { var gCtx *gin.Context // // setup context // repository := \u0026amp;MockedUserRepository{} repository.filterByLastnameFunc = func(ctx context.Context, lastname string) ([]User, error) { if ctx != gCtx { t.Error(\u0026#34;expected other context\u0026#34;) } if lastname != \u0026#34;some last name\u0026#34; { t.Error(\u0026#34;expected other lastname\u0026#34;) } return nil, errors.New(\u0026#34;error\u0026#34;) } controller := NewAdminController(repository) controller.FilterByLastname(gCtx) // // do some checking for ctx // } This technique can be beneficial when testing our code\u0026rsquo;s integration with AWS services, such as SQS, using the AWS SDK. In this case, our SQSReceiver depends on the SQSAPI interface, which has many functions:\nSQSReceiver\nimport ( // // some imports // \u0026#34;github.com/aws/aws-sdk-go/service/sqs/sqsiface\u0026#34; ) type SQSReceiver struct { sqsAPI sqsiface.SQSAPI } func (r *SQSReceiver) Run() { // // wait for SQS message // } Here we can use the same technique and provide our own mocked struct:\nMockedSQSAPI\ntype MockedSQSAPI struct { sqsiface.SQSAPI sendMessageFunc func(input *sqs.SendMessageInput) (*sqs.SendMessageOutput, error) } func (m *MockedSQSAPI) SendMessage(input *sqs.SendMessageInput) (*sqs.SendMessageOutput, error) { return m.sendMessageFunc(input) } Test SQSReceiver\nfunc TestSQSReceiver(t *testing.T) { // // setup context // sqsAPI := \u0026amp;MockedSQSAPI{} sqsAPI.sendMessageFunc = func(input *sqs.SendMessageInput) (*sqs.SendMessageOutput, error) { if input.MessageBody == nil || *input.MessageBody != \u0026#34;content\u0026#34; { t.Error(\u0026#34;expected other message\u0026#34;) } return nil, errors.New(\u0026#34;error\u0026#34;) } receiver := \u0026amp;SQSReceiver{ sqsAPI: sqsAPI, } receiver.Run() // // do some checking for ctx // } In general, I don\u0026rsquo;t usually test infrastructural objects responsible for establishing connections with databases or external services. For such cases, I prefer to write tests at a higher level of the testing pyramid. However, if there is a genuine need to test such code, this approach has been helpful to me.\nMocking of Function # In core Go code or within other packages, there are many useful functions available. We can use these functions directly in our code, as demonstrated in the ConfigurationRepository below. This struct is responsible for reading the config.yml file and returning the configuration used throughout the application. ConfigurationRepository calls the ReadFile method from the core Go package OS:\nUsage of method ReadFile\ntype ConfigurationRepository struct { // // some fields\t// } func (r *ConfigurationRepository) GetConfiguration() (map[string]string, error) { config := map[string]string{} data, err := os.ReadFile(\u0026#34;config.yml\u0026#34;) // // do something with data // return config, nil } In code like this, when we want to test GetConfiguration, it becomes necessary to depend on the presence of the config.yml file for each test execution. This means relying on technical details, such as reading from files. In such situations, I have used two different approaches in the past to provide unit tests for this code.\nVariation 1: Simple Type Aliasing # The first approach is to create a type alias for the method type that we want to mock. This new type represents the function signature we want to use in our code. In this case, ConfigurationRepository should depend on this new type, FileReaderFunc, instead of the method we want to mock:\nUse FileReaderFunc\ntype FileReaderFunc func(filename string) ([]byte, error) type ConfigurationRepository struct { fileReaderFunc FileReaderFunc // // some fields // } func NewConfigurationRepository(fileReaderFunc FileReaderFunc) ConfigurationRepository{ return ConfigurationRepository{ fileReaderFunc: fileReaderFunc, } } func (r *ConfigurationRepository) GetConfiguration() (map[string]string, error) { config := map[string]string{} data, err := r.fileReaderFunc(\u0026#34;config.yml\u0026#34;) // // do something with data // return config, nil } In this case, when initializing our application, we would pass the actual method from the Go core package as an argument during the creation of ConfigurationRepository:\nMain function\npackage main func main() { repository := NewConfigurationRepository(ioutil.ReadFile) config, err := repository.GetConfiguration() // // do something with configuration // } Finally, we can write a unit test as shown in the code example below. Here, we define a new FileReaderFunc function that returns the result we control in each of the cases.\nTest with FileReaderFunc\nfunc TestGetConfiguration(t *testing.T) { var readerFunc FileReaderFunc // we want to have error from reader readerFunc = func(filename string) ([]byte, error) { return nil, errors.New(\u0026#34;error\u0026#34;) } repository := NewConfigurationRepository(readerFunc) _, err := repository.GetConfiguration() if err == nil { t.Error(\u0026#34;error is expected\u0026#34;) } // we want to have concrete result from reader readerFunc = func(filename string) ([]byte, error) { return []byte(\u0026#34;content\u0026#34;), nil } repository = NewConfigurationRepository(readerFunc) _, err = repository.GetConfiguration() if err != nil { t.Error(\u0026#34;error is not expected\u0026#34;) } // // do something with config // } Variation 2: Complex Type Aliasing with Interface # The second variation employs the same concept but relies on an interface as a dependency in ConfigurationRepository. Instead of depending on a function type, it depends on an interface FileReader, which has a method with the same signature as the ReadFile method we want to mock.\nUse FileReader interface\ntype FileReader interface { ReadFile(filename string) ([]byte, error) } type ConfigurationRepository struct { fileReader FileReader // // some fields // } func NewConfigurationRepository(fileReader FileReader) *ConfigurationRepository { return \u0026amp;ConfigurationRepository{ fileReader: fileReader, } } func (r *ConfigurationRepository) GetConfiguration() (map[string]string, error) { config := map[string]string{} data, err := r.fileReader.ReadFile(\u0026#34;config.yml\u0026#34;) // // do something with data // return config, nil } At this point, we should once again create the same type alias, FileReaderFunc, but this time we should attach a function to that type. Yes, we need to add a method to a method (I cannot express how much I appreciate this aspect in Go).\nNew FileReaderFunc\ntype FileReaderFunc func(filename string) ([]byte, error) func (f FileReaderFunc) ReadFile(filename string) ([]byte, error) { return f(filename) } From this point, the FileReaderFunc type implements the FileReader interface. The sole method it contains forwards the call to the instance of that type, which is the original method. This results in minimal changes when initializing the application:\nNew Main function\nfunc main() { repository := NewConfigurationRepository(FileReaderFunc(ioutil.ReadFile)) config, err := repository.GetConfiguration() // // do something with configuration // } And, it does not carry any change to unit test:\nTest with FileReader\nfunc TestGetConfiguration(t *testing.T) { var readerFunc FileReaderFunc // we want to have error from reader readerFunc = func(filename string) ([]byte, error) { return nil, errors.New(\u0026#34;error\u0026#34;) } repository := NewConfigurationRepository(readerFunc) _, err := repository.GetConfiguration() if err == nil { t.Error(\u0026#34;error is expected\u0026#34;) } // we want to have concrete result from reader readerFunc = func(filename string) ([]byte, error) { return []byte(\u0026#34;content\u0026#34;), nil } repository = NewConfigurationRepository(readerFunc) config, err := repository.GetConfiguration() if err != nil { t.Error(\u0026#34;error is not expected\u0026#34;) } // // do something with config // } I prefer the second variation, as someone who is more inclined toward using interfaces and structs rather than independent functions. However, both of these solutions are valid choices.\nBonus 1: Mocking HTTP server # When it comes to mocking an HTTP server, I believe it goes beyond unit testing. However, there may be situations where your code structure depends on HTTP requests, and this section provides some ideas for handling such scenarios. Let\u0026rsquo;s consider a UserAPIRepository that sends and retrieves data by interacting with an external API rather than a database. This struct may look something like this:\nUserAPIRepository\ntype UserAPIRepository struct { host string } func NewUserAPIRepository(host string) *UserAPIRepository { return \u0026amp;UserAPIRepository{ host: host, } } func (r *UserAPIRepository) FilterByLastname(ctx context.Context, lastname string) ([]User, error) { var users []User url := path.Join(r.host, \u0026#34;/users/\u0026#34;, lastname) response, err := http.Get(url) // // do somethinf with users // return users, nil } Naturally, we could also handle this by mocking functions, but let\u0026rsquo;s explore this approach. To create a unit test for UserAPIRepository, we can use an instance of Server from the core Go HTTPtest package. This package offers a simple local server that runs on specific ports and can be easily customized for our test cases, allowing us to send requests to it:\nTest UserAPIRepository\nimport ( // // some imports // \u0026#34;net/http/httptest\u0026#34; ) func TestUserAPIRepository(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, \u0026#34;/users/\u0026#34;) { var content string // // do something // io.WriteString(w, content) return } http.NotFound(w, r) })) repository := NewUserAPIRepository(server.URL) users, err := repository.FilterByLastname(context.Background(), \u0026#34;some last name\u0026#34;) // // do some checking for users and err // } At this point, I would like to mention that a much better approach for testing integration with an external API is to use contract testing.\nBonus 2: Mocking SQL Database # Again, like for HTTP requests, I\u0026rsquo;m not particularly eager to write unit tests for testing SQL queries. I always question whether I\u0026rsquo;m testing a repository or a mocking tool. Still, when I want to check some SQL query, it is probably wrapped in some struct, like here in UserDBRepository:\nUserDBRepository\ntype UserDBRepository struct { connection *sql.DB } func NewUserDBRepository(connection *sql.DB) *UserDBRepository { return \u0026amp;UserDBRepository{ connection: connection, } } func (r *UserDBRepository) FilterByLastname(ctx context.Context, lastname string) ([]User, error) { var users []User rows, err := r.connection.Query(\u0026#34;SELECT * FROM users WHERE lastname = ?\u0026#34;, lastname) // // do something with users // return users, nil } When I decide to write unit tests for this kind of repositories, I like to use the package Sqlmock. It is simple enough and has excellent documentation.\nTest UserDBRepository with Sqlmock\nimport ( // // some imports // \u0026#34;github.com/DATA-DOG/go-sqlmock\u0026#34; ) func TestUserDBRepository(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Error(\u0026#34;expected not to have error\u0026#34;) } mock. ExpectQuery(\u0026#34;SELECT * FROM users WHERE lastname = ?\u0026#34;). WithArgs(\u0026#34;some last name\u0026#34;). WillReturnError(errors.New(\u0026#34;error\u0026#34;)) repository := NewUserDBRepository(db) users, err := repository.FilterByLastname(context.Background(), \u0026#34;some last name\u0026#34;) // // do some checking for users and err // } When mocking actual SQL queries becomes too exhausting, another approach is to use a small SQLite file with test data. This file should have the same table structure as our regular SQL database. However, this is not an ideal solution because we might test our queries on different database engines, and it\u0026rsquo;s better to depend on an ORM to avoid double integration. In this case, I create a temporary file and copy data from the SQLite file into it before each test execution. It is slower, but this way, I can avoid corrupting my test data.\nTest data with SQLite\nimport ( // // some imports // _ \u0026#34;github.com/mattn/go-sqlite3\u0026#34; ) func getSqliteDBWithTestData() (*sql.DB, error) { // read all from sqlite file data, err := ioutil.ReadFile(\u0026#34;test_data.sqlite\u0026#34;) if err != nil { return nil, err } // create temporary file tmpFile, err := ioutil.TempFile(\u0026#34;\u0026#34;, \u0026#34;db*.sqlite\u0026#34;) if err != nil { return nil, err } // store test data into temporary file _, err = tmpFile.Write(data) if err != nil { return nil, err } err = tmpFile.Close() if err != nil { return nil, err } // make connection to temporary file db, err := sql.Open(\u0026#34;sqlite3\u0026#34;, tmpFile.Name()) if err != nil { return nil, err } return db, nil } Finally, the unit test looks much more straightforward now:\nTest UserDBRepository with test data\nfunc TestUserDBRepository(t *testing.T) { db, err := getSqliteDBWithTestData() if err != nil { t.Error(\u0026#34;expected not to have error\u0026#34;) } repository := NewUserDBRepository(db) users, err := repository.FilterByLastname(context.Background(), \u0026#34;some last name\u0026#34;) // // do some checking for users and err // } Conclusion # Writing unit tests in Go can be more challenging compared to other languages, at least in my experience. It involves preparing the code to support the testing strategy. Surprisingly, I find this part enjoyable because it has helped me refine my architectural approach to coding more than any other language. It\u0026rsquo;s never boring, and there\u0026rsquo;s a constant sense of satisfaction, even after writing thousands of unit tests.\nUseful Resources # Martin Fowler Go Dev ","date":"20 September 2023","externalUrl":null,"permalink":"/article/golang/tutorial-unit-testing-mocking/","section":"Articles","summary":"Unit testing has always been my thing, almost like a hobby. There was a time when I was obsessed with it, and I made sure that all my projects had at least 90% unit test coverage. You can probably imagine how much time it can take to make such a significant change in the codebase. However, the result was worth it because I rarely encountered bugs related to business logic. Most of the issues were related to integration problems with other services or databases.\nAdding new business rules was a breeze because there were already tests in place to cover all the cases from before. The key was to ensure that these tests remained successful in the end. Sometimes, I didn’t even need to check the entire running service; having the new and old unit tests pass was sufficient.\nOnce, while working on a personal project, I had to write unit tests to cover numerous Go structs and functions—more than 100 in total. It consumed my entire weekend, and late on a Sunday night, before heading out on a business trip the next day, I set an alarm clock to wake me up. I had hardly slept that night; it was one of those restless nights when you dream but are also aware of yourself and your surroundings. My brain was active the entire time, and in my dreams, I kept writing unit tests for my alarm clock. To my surprise, each time I executed a unit test in my dream, the alarm rang. It continued ringing throughout the night.\nAnd yes, I almost forgot to mention, for two years, we had zero bugs in production. The application continued to fetch all the data and send all the emails every Monday. I don’t even remember my Gitlab password anymore.\nUnit Testing and Mocking (in general) # In Martin Fowler’s article, we can identify two types of unit tests:\nSociable unit tests, where we test a unit while it relies on other objects in conjunction with it. For example, if we want to test the UserController, we would test it along with the UserRepository, which communicates with the database.\nSolitary unit tests, where we test a unit in complete isolation. In this scenario, we would test the UserController, which interacts with a controlled, mocked UserRepository. With mocking, we can specify how it behaves without involving a database.\n","title":"Golang Tutorial: Unit Testing with Mocking","type":"article"},{"content":"How often do we encounter significant changes in our preferred programming language? Some languages undergo frequent updates, while others remain traditional and stable. Go falls into the latter category, known for its consistency. \u0026ldquo;This is not the Go way!\u0026rdquo; is a phrase that often comes to mind. Most Go releases have focused on refining its existing principles. However, a major shift is on the horizon. The Go team has announced that Generics in Go are becoming a reality, moving beyond mere discussion and into implementation.\nBrace yourselves, a revolution is coming.\nWhat are Generics? # Generics allow us to parameterize types when defining interfaces, functions, and structs.\nGenerics is not a new concept. It has been used since the first version of Ada, through templates in C++, to its modern implementations in Java and C#. To illustrate without delving into complex definitions, let\u0026rsquo;s examine a practical example. Instead of having multiple Max or Min functions like this:\nWithout Generics\nfunc MaxInt(a, b int) int { // some code } func MaxFloat64(a, b float64) float64 { // some code } func MaxByte(a, b byte) byte { // some code } we can declare now only one method, like this:\nWith Generics\nfunc Max[T constraints.Ordered](a, b T) T { // some code } Wait, what just happened? Instead of defining a method for each type in Go, we utilized Generics. We used a generic type, parameter T, as an argument for the method. With this minor adjustment, we can support all orderable types. The parameter T can represent any type that satisfies the Ordered constraint (we will discuss constraints later). Initially, we need to specify what kind of type T is. Next, we determine where we want to use this parameterized type. In this case, we\u0026rsquo;ve specified that both input arguments and the output should be of type T. If we execute the method by defining T as an integer, then everything here will be an integer:\nExecute Generic Function\nfunc main() { fmt.Println(Max[int](1, 2)) } // // this code behaves exactly like method: // Max(a, b int) int And it doesn\u0026rsquo;t stop there. We can provide as many parameterized types as we need and assign them to different input and output arguments as desired:\nExecute some complex Generic Function\nfunc Do[R any, S any, T any](a R, b S) T { // some code } func main() { fmt.Println(Do[int, uint, float64](1, 2)) } // // this code behaves exactly like method: // Do(a int, b uint) float64 Here we have three parameters: R, S, and T. As we can see from the any constraint (which behaves like interface{}), those types can be, well, anything. So, up to this point, we should have a clear understanding of what generics are and how we use them in Go. Let\u0026rsquo;s now focus on more exciting consequences.\nSpeed, give me what I need # Generics in Go are not the same as reflection.\nBefore delving into complex examples, it\u0026rsquo;s essential to check the benchmark scores for generics. Logically, we do not expect performance similar to reflection because if that were the case, we would not need generics at all. Generics are not in any way comparable to reflection and were never intended to be. If anything, generics are an alternative for code generation in some use cases. Our expectation is that code based on generics should have similar benchmark results as code executed in a more traditional way. So, let\u0026rsquo;s examine a basic case:\nA Generic Function for Benchmark\npackage main import ( \u0026#34;constraints\u0026#34; \u0026#34;fmt\u0026#34; ) type Number interface { constraints.Integer | constraints.Float } func Transform[S Number, T Number](input []S) []T { output := make([]T, 0, len(input)) for _, v := range input { output = append(output, T(v)) } return output } func main() { fmt.Printf(\u0026#34;%#v\u0026#34;, Transform[int, float64]([]int{1, 2, 3, 6})) } // // // Out: // []float64{1, 2, 3, 6} Here are small methods for transforming one Number type to another. Number is our constraint, built on the Integer and the Float constraints from the Go standard library (we will cover this topic later). Number can be any numerical type in Go, from any derivative of int to uint, float, and so on. The Transform methods expect a slice with the first parametrized numerical type S as the slice\u0026rsquo;s base and transform it into a slice with the second parametrized type T as the slice\u0026rsquo;s base. In short, if we want to transform a slice of ints into a slice of floats, we would call this method as we do in the main function. The non-generics alternative for our function would be a method that expects a slice of ints and returns a slice of floats. So, that is what we will test in our benchmark:\nBenchmark\nfunc BenchmarkGenerics(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { Transform[int, float64]([]int{1, 2, 3, 6}) } } func TransformClassic(input []int) []float64 { output := make([]float64, 0, len(input)) for _, v := range input { output = append(output, float64(v)) } return output } func BenchmarkClassic(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { TransformClassic([]int{1, 2, 3, 6}) } } // // // Out: // goos: darwin // goarch: amd64 // pkg: test/generics // cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz // // first run: // BenchmarkGenerics // BenchmarkGenerics-8 38454709\t31.80 ns/op // BenchmarkClassic // BenchmarkClassic-8 36445143\t34.83 ns/op // PASS // // second run: // BenchmarkGenerics // BenchmarkGenerics-8 34619782\t33.48 ns/op // BenchmarkClassic // BenchmarkClassic-8 36784915\t31.78 ns/op // PASS // // third run: // BenchmarkGenerics // BenchmarkGenerics-8 36157389\t33.38 ns/op // BenchmarkClassic // BenchmarkClassic-8 37115414\t32.30 ns/op // PASS No surprises here. The execution time is practically the same for both methods, so using generics does not impact the performance of our application. But are there any repercussions for structs? Let\u0026rsquo;s try that. Now, we will use structs and attach methods to them. The task will be the same — converting one slice into another:\nAnother Benchmark\nfunc BenchmarkGenerics(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { Transform[int, float64]([]int{1, 2, 3, 6}) } } func TransformClassic(input []int) []float64 { output := make([]float64, 0, len(input)) for _, v := range input { output = append(output, float64(v)) } return output } func BenchmarkClassic(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { TransformClassic([]int{1, 2, 3, 6}) } } // // // Out: // goos: darwin // goarch: amd64 // pkg: test/generics // cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz // // first run: // BenchmarkGenerics // BenchmarkGenerics-8 38454709\t31.80 ns/op // BenchmarkClassic // BenchmarkClassic-8 36445143\t34.83 ns/op // PASS // // second run: // BenchmarkGenerics // BenchmarkGenerics-8 34619782\t33.48 ns/op // BenchmarkClassic // BenchmarkClassic-8 36784915\t31.78 ns/op // PASS // // third run: // BenchmarkGenerics // BenchmarkGenerics-8 36157389\t33.38 ns/op // BenchmarkClassic // BenchmarkClassic-8 37115414\t32.30 ns/op // PASS Again, no surprises. Using generics or the classic implementation does not have any impact on the performance of the Go code. Yes, it is true that we did not test too complex cases, but if there were a significant difference, we would have already noticed it. So, we are safe to proceed.\nConstraints # If we want to test more complex examples, simply adding any parametrized type and running the application is not enough. If we decide to create a simple example with some variables without any complex calculations, we will not need to add anything special:\nA simple Generic Function\nfunc Max[T interface{}](a, b T) (T, T) { return a, b } func main() { fmt.Println(Max(1, 2)) fmt.Println(Max(3.0, 2.0)) } // // // Out: // 1 2 // 3 2 If we want to test more complex examples, simply adding any parameterized type and running the application is not enough. Suppose we decide to create a simple example with some variables without any complex calculations. In that case, we will not need to add anything special, except that our method Max does not calculate the maximum value of its inputs but returns them both. There is nothing strange in the example above.\nTo achieve this, we use a parameterized type T, defined as interface{}. In this example, we should not view interface{} as a type but as a constraint. We use constraints to define rules for our parameterized types and provide the Go compiler with some context on what to expect. To reiterate, we do not use interface{} here as a type but as a constraint. We define rules for the parameterized type, and in this case, that type must support whatever interface{} does. So, practically, we could also use the any constraint here. (To be honest, in all the examples, I have preferred interface{} instead of any, to respect the \u0026ldquo;good old days\u0026rdquo;.)\nDuring compile-time, the compiler can take a constraint and use it to check if the parameterized type supports operators and methods that we want to execute in the following code. As the compiler does most of the optimization at runtime (and therefore, we do not impact runtime, as we could see in the benchmark), it allows only the operators and functions defined for particular constraints. So, to understand the importance of constraints, let us finish implementing the Max method and try to compare the a and b variables:\nFailed execution of Generic Function\nfunc Max[T any](a, b T) T { if a \u0026gt; b { return a } return b } func main() { fmt.Println(Max(1, 2)) fmt.Println(Max(3.0, 2.0)) } // // // Out: // ./main.go:6:5: invalid operation: cannot compare a \u0026gt; b (operator \u0026gt; not defined on T) When we attempt to run the application, we encounter an error — \u0026ldquo;operator \u0026gt; not defined on T.\u0026rdquo; Since we defined the T type as any, the final type can be, well, anything. At this point, the compiler does not know how to handle the \u0026gt; operator.\nTo resolve this issue, we must define the parameterized type T as a constraint that allows such an operator. Fortunately, thanks to the Go team, we have the Constraints package, which includes such a constraint. The constraint we want to use is called Ordered, and after making this adjustment, our code works perfectly:\nOrdered Constraint\nfunc Max[T constraints.Ordered](a, b T) T { if a \u0026gt; b { return a } return b } func main() { fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max(1, 2))) fmt.Println(Max(1, 2)) fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max(3.0, 2.0))) fmt.Println(Max(3.0, 2.0)) fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max[int](1, 2))) fmt.Println(Max[int](1, 2)) fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max[int64](1, 2))) fmt.Println(Max[int64](1, 2)) fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max[float64](3.0, 2.0))) fmt.Println(Max[float64](3.0, 2.0)) fmt.Println(fmt.Sprintf(\u0026#34;%T\u0026#34;, Max[float32](3.0, 2.0))) fmt.Println(Max[float32](3.0, 2.0)) } // // // Out: // int --\u0026gt; Max(1, 2) // 2 // float64 --\u0026gt; Max(3.0, 2.0) // 3 // int --\u0026gt; Max[int](1, 2) // 2 // int64 --\u0026gt; Max[int64](1, 2) // 2 // float64 --\u0026gt; Max[float64](3.0, 2.0) // 3 // float32 --\u0026gt; Max[float32](3.0, 2.0) // 3 By using the Ordered constraint, we achieved the desired result. One interesting aspect of this example is how the compiler interprets the final type T, depending on the values we pass to the method. Without specifying the actual type in square brackets, as shown in the first two cases, the compiler can deduce the type used for the arguments — in the case of Go, this would be int and float64.\nHowever, if we intend to use types other than the default ones, such as int64 or float32, we should explicitly provide these types in square brackets. This way, we give the compiler precise information about what to expect. If we wish, we can extend the functionality of the Max function to support finding the maximum value within an array:\nZero values\nfunc Max[T constraints.Ordered](a []T) (T, error) { if len(a) == 0 { return T(0), errors.New(\u0026#34;empty array\u0026#34;) } max := a[0] for i := 1; i \u0026lt; len(a); i++ { if a[i] \u0026gt; max { max = a[i] } } return max, nil } func main() { fmt.Println(Max([]string{})) fmt.Println(Max([]string{\u0026#34;z\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;f\u0026#34;})) fmt.Println(Max([]int{1, 2, 5, 3})) fmt.Println(Max([]float32{4.0, 5.0, 2.0})) fmt.Println(Max([]float32{})) } // // // Out: // empty array // z \u0026lt;nil\u0026gt; // 5 \u0026lt;nil\u0026gt; // 5 \u0026lt;nil\u0026gt; // 0 empty array In this example, two interesting points emerge:\nAfter defining type T within square brackets, we can use it in various ways within the function signature, whether as a simple type, a slice type, or even as part of a map.\nTo return the zero value of a specific type, we can use T(0). The Go compiler is intelligent enough to convert the zero value into the desired type, such as an empty string in the first case.\nWe\u0026rsquo;ve also seen how constraints work when comparing values of a certain type. With the Ordered constraint, we can use any operator defined on integers, floats, and strings. However, if we wish to use the == operator exclusively, we can utilize the new reserved word comparable, a unique constraint that only supports this operator and nothing else:\nComparable Constraint\nfunc Equal[T comparable](a, b T) bool { return a == b } func Dummy[T any](a, b T) (T, T) { return a, b } func main() { fmt.Println(Equal(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;)) fmt.Println(Equal(\u0026#34;a\u0026#34;, \u0026#34;a\u0026#34;)) fmt.Println(Equal(1, 2)) fmt.Println(Equal(1, 1)) fmt.Println(Dummy(5, 6)) fmt.Println(Dummy(\u0026#34;e\u0026#34;, \u0026#34;f\u0026#34;)) } // // // Out: // false // true // false // true // 5 6 // e f In the example above, we can observe the proper usage of the comparable constraint. It\u0026rsquo;s worth noting that the compiler can infer the actual types even without the need to strictly define them within square brackets.\nAn interesting point to highlight in this example is that we used the same letter, T, for both parameterized types in two different methods, Equal and Dummy. It\u0026rsquo;s important to understand that each T type is defined within the scope of its respective method (or struct and its methods), and these T types do not refer to the same type outside of their respective scopes. This means that you can use the same letter T in different methods, and the types will remain independent of each other.\nCustom constraints # Creating custom constraints in Go is straightforward. You can define a constraint as any type, but using an interface is often the best choice. Here\u0026rsquo;s how you can do it:\nCustom Constraint\ntype Greeter interface { Greet() } func Greetings[T Greeter](t T) { t.Greet() } type EnglishGreeter struct{} func (g EnglishGreeter) Greet() { fmt.Println(\u0026#34;Hello!\u0026#34;) } type GermanGreeter struct{} func (g GermanGreeter) Greet() { fmt.Println(\u0026#34;Hallo!\u0026#34;) } func main() { Greetings(EnglishGreeter{}) Greetings(GermanGreeter{}) } // // // Out: // Hello! // Hallo! We\u0026rsquo;ve created an interface called Greeter to use it as a constraint in the Greetings method. While we could use a Greeter variable directly instead of generics, we\u0026rsquo;ve used generics here for demonstration purposes.\nType sets # Every type has an associated type set. The type set of an ordinary non-interface type T consists of just T itself, represented as the set {T}. In the case of an interface type (for this discussion, we are focusing solely on ordinary interface types without type lists), the type set comprises all types that declare all the methods of that interface.\nThe definition mentioned above comes from a proposal regarding type sets, and it has already been incorporated into the Go source code. This significant change has opened up new possibilities for us. Notably, our interface types can now embed primitive types such as int, float64, and byte, not limited to other interfaces. This enhancement allows us to define more versatile constraints. Let\u0026rsquo;s explore the following example:\nCustom Comparable Constraint as a Type Set\ntype Comparable interface { ~int | float64 | rune } func Compare[T Comparable](a, b T) bool { return a == b } type customInt int func main() { fmt.Println(Compare(1, 2)) fmt.Println(Compare(customInt(1), customInt(1))) fmt.Println(Compare(\u0026#39;a\u0026#39;, \u0026#39;a\u0026#39;)) fmt.Println(Compare(1.0, 2.0)) } // // // Out: // false // true // true // false We\u0026rsquo;ve defined our Comparable constraint, and it might appear a bit unusual, doesn\u0026rsquo;t it? The new approach with type sets in Go now allows us to create an interface that represents a union of types. To specify a union between two types, we simply include them within the interface and use the | operator to separate them. In our example, the Comparable interface constitutes a union of types: rune, float64, and indeed, int. However, int is designated here as an approximation element.\nAs demonstrated in the proposal for type sets, the type set of an approximation element T encompasses not just type T itself but also all types whose underlying type is T. Consequently, by employing the ~int approximation element, we can supply variables of our customInt type to the Compare method. You\u0026rsquo;ll notice that we\u0026rsquo;ve defined customInt as a custom type with int as its underlying type. If we neglect to include the ~ operator, the compiler will issue an error, preventing the execution of the application. This represents a notable advancement in our understanding of these concepts.\nHow far can we go? # We have the freedom to go wherever we please. Seriously, this feature has revolutionized the language. I mean, new code is constantly emerging, and this could have a significant impact on packages that rely on code generation, such as Ent. Starting from the standard library, I can already envision many codes being refactored in future versions to incorporate generics. Generics could even pave the way for the development of an ORM, similar to what we\u0026rsquo;re accustomed to seeing in Doctrine, for instance.\nLet\u0026rsquo;s take a model from the Gorm package as an example:\nGorm Example\ntype ProductGorm struct { gorm.Model Name string Price uint } type UserGorm struct { gorm.Model FirstName string LastName string } Imagine that we want to implement the Repository pattern in Go for both models (ProductGorm and UserGorm). With the current stable version of Go, we can only choose one of the following solutions:\nWrite two separate Repository structs. Write a code generator that uses a template to create those two Repository structs. Decide not to use the Repository pattern. Now, with generics, the horizon of opportunities has shifted towards a more flexible approach, and we can do something like this:\nGorm and Generics\ntype Repository[T any] struct { db *gorm.DB } func (r *Repository[T]) Create(t T) error { return r.db.Create(\u0026amp;t).Error } func (r *Repository[T]) Get(id uint) (*T, error) { var t T err := r.db.Where(\u0026#34;id = ?\u0026#34;, id).First(\u0026amp;t).Error return \u0026amp;t, err } So, we have our Repository struct with a parameterized type T, which can be anything. It\u0026rsquo;s worth noting that we defined T only in the Repository type definition, and we simply assigned its associated functions. In this example, we have only two methods, Create and Get, for demonstration purposes. To simplify our demonstration, let\u0026rsquo;s create two separate methods for initializing different Repositories:\nDeclare new Repositories\nfunc NewProductRepository(db *gorm.DB) *Repository[ProductGorm] { db.AutoMigrate(\u0026amp;ProductGorm{}) return \u0026amp;Repository[ProductGorm]{ db: db, } } func NewUserRepository(db *gorm.DB) *Repository[UserGorm] { db.AutoMigrate(\u0026amp;UserGorm{}) return \u0026amp;Repository[UserGorm]{ db: db, } } These two methods return instances of Repositories with predefined types, essentially serving as shortcuts. Now, let\u0026rsquo;s conduct the final test of our small application:\nTest new Repositories\nfunc main() { db, err := gorm.Open(sqlite.Open(\u0026#34;test.db\u0026#34;), \u0026amp;gorm.Config{}) if err != nil { panic(\u0026#34;failed to connect database\u0026#34;) } productRepo := NewProductRepository(db) productRepo.Create(ProductGorm{ Name: \u0026#34;product\u0026#34;, Price: 100, }) fmt.Println(productRepo.Get(1)) userRepo := NewUserRepository(db) userRepo.Create(UserGorm{ FirstName: \u0026#34;first\u0026#34;, LastName: \u0026#34;last\u0026#34;, }) fmt.Println(userRepo.Get(1)) } // // // Out: // \u0026amp;{{1 2021-11-23 22:50:14.595342 +0100 +0100 2021-11-23 22:50:14.595342 +0100 +0100 {0001-01-01 00:00:00 +0000 UTC false}} 100} \u0026lt;nil\u0026gt; // \u0026amp;{{1 2021-11-23 22:50:44.802705 +0100 +0100 2021-11-23 22:50:44.802705 +0100 +0100 {0001-01-01 00:00:00 +0000 UTC false}} first last} \u0026lt;nil\u0026gt; And it works! One implementation for Repository that supports two models, all without the need for reflection or code generation. This is something I never thought I would see in Go.\nConclusion # There\u0026rsquo;s no doubt that Generics in Go are a monumental change. This change has the potential to significantly alter how Go is used and may lead to numerous refactors within the Go community in the near future. While I\u0026rsquo;ve been experimenting with generics on a daily basis, exploring their possibilities, I can\u0026rsquo;t wait to see them in the stable Go version. Viva la Revolution!\nUseful Resources # Go 1.18 Release Notes ","date":"19 September 2023","externalUrl":null,"permalink":"/article/golang/tutorial-generics/","section":"Articles","summary":"How often do we encounter significant changes in our preferred programming language? Some languages undergo frequent updates, while others remain traditional and stable. Go falls into the latter category, known for its consistency. “This is not the Go way!” is a phrase that often comes to mind. Most Go releases have focused on refining its existing principles. However, a major shift is on the horizon. The Go team has announced that Generics in Go are becoming a reality, moving beyond mere discussion and into implementation.\nBrace yourselves, a revolution is coming.\nWhat are Generics? # Generics allow us to parameterize types when defining interfaces, functions, and structs.\nGenerics is not a new concept. It has been used since the first version of Ada, through templates in C++, to its modern implementations in Java and C#. To illustrate without delving into complex definitions, let’s examine a practical example. Instead of having multiple Max or Min functions like this:\nWithout Generics\nfunc MaxInt(a, b int) int { // some code } func MaxFloat64(a, b float64) float64 { // some code } func MaxByte(a, b byte) byte { // some code } we can declare now only one method, like this:\nWith Generics\nfunc Max[T constraints.Ordered](a, b T) T { // some code } Wait, what just happened? Instead of defining a method for each type in Go, we utilized Generics. We used a generic type, parameter T, as an argument for the method. With this minor adjustment, we can support all orderable types. The parameter T can represent any type that satisfies the Ordered constraint (we will discuss constraints later). Initially, we need to specify what kind of type T is. Next, we determine where we want to use this parameterized type. In this case, we’ve specified that both input arguments and the output should be of type T. If we execute the method by defining T as an integer, then everything here will be an integer:\nExecute Generic Function\nfunc main() { fmt.Println(Max[int](1, 2)) } // // this code behaves exactly like method: // Max(a, b int) int And it doesn’t stop there. We can provide as many parameterized types as we need and assign them to different input and output arguments as desired:\nExecute some complex Generic Function\nfunc Do[R any, S any, T any](a R, b S) T { // some code } func main() { fmt.Println(Do[int, uint, float64](1, 2)) } // // this code behaves exactly like method: // Do(a int, b uint) float64 Here we have three parameters: R, S, and T. As we can see from the any constraint (which behaves like interface{}), those types can be, well, anything. So, up to this point, we should have a clear understanding of what generics are and how we use them in Go. Let’s now focus on more exciting consequences.\n","title":"Golang Tutorial: Generics","type":"article"},{"content":"","date":"18 September 2023","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"Learning a new programming language is often a straightforward process. I often hear: “The first programming language you learn in a year. The second one in a month. The third one in a week, and then each next one in a day.” Saying that is an exaggeration, but it is not too distant from the truth in some cases. For example, jumping to a language relatively similar to the previous one, like Java and C#, can be a straightforward process. But sometimes, switching is tricky, even when we switch from one Object-Oriented language to another. Many features influence such transitions, like strong or weak types, if a language has interfaces, abstract classes, or classes at all. Some of those difficulties we experience immediately after switching, and we adopt a new approach. But some issues we experience later, during unit testing, for example. And then, we learn why The Dependency Inversion Principle is essential, especially in Go.\nWhen we do not respect The Dependency Inversion # High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.\nAbove is the definition of DIP as presented by Uncle Bob in his paper. There are also more details inside his blog. So, how can we understand this, especially in the context of Go? First, we should accept Abstraction as an object-oriented programming concept. We use this concept to expose essential behaviors and hide the details of their implementation.\nSecond, what are high and low-level modules? In the context of Go, high-level modules are software components used at the top of the application, such as code used for presentation. It can also be code close to the top level, like code for business logic or some use-case components. It is essential to understand it as a layer that provides real business value to our application. On the other hand, low-level software components are mostly small code pieces that support the higher level. They hide technical details about different infrastructural integrations. For example, this could be a struct that contains the logic for retrieving data from the database, sending an SQS message, fetching a value from Redis, or sending an HTTP request to an external API. So, what does it look like when we break The Dependency Inversion Principle, and our high-level component depends on one low-level component?\nLet\u0026rsquo;s examine the following example:\nThe Infrastructure Layer\ntype UserRepository struct { db *gorm.DB } func NewUserRepository(db *gorm.DB) *UserRepository { return \u0026amp;UserRepository{ db: db, } } func (r *UserRepository) GetByID(id uint) (*domain.User, error) { user := domain.User{} err := r.db.Where(\u0026#34;id = ?\u0026#34;, id).First(\u0026amp;user).Error if err != nil { return nil, err } return \u0026amp;user, nil } The Domain Layer\ntype User struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` // some fields } The Application Layer\ntype EmailService struct { repository *infrastructure.UserRepository // some email sender } func NewEmailService(repository *infrastructure.UserRepository) *EmailService { return \u0026amp;EmailService{ repository: repository, } } func (s *EmailService) SendRegistrationEmail(userID uint) error { user, err := s.repository.GetByID(userID) if err != nil { return err } // send email return nil } In the code snippet above, we defined a high-level component, EmailService. This struct belongs to the application layer and is responsible for sending an email to newly registered customers. The idea is to have a method, SendRegistrationEmail, which expects the ID of a User. In the background, it retrieves a User from UserRepository, and later (probably) it delivers it to some EmailSender service to execute email delivery. The part with EmailSender is currently out of our focus. Let\u0026rsquo;s concentrate on UserRepository instead. This struct represents a repository that communicates with a database, so it belongs to the infrastructure layer. It appears that our high-level component, EmailService, depends on the low-level component, UserRepository. In practice, without defining a connection to the database, we cannot initialize our use-case struct. Such an anti-pattern immediately impacts our unit testing in Go.\nLet\u0026rsquo;s assume we want to test EmailService, as shown in the code snippet below:\nUnit Tests for EmailService\nimport ( \u0026#34;testing\u0026#34; // some dependencies \u0026#34;github.com/DATA-DOG/go-sqlmock\u0026#34; \u0026#34;github.com/stretchr/testify/assert\u0026#34; \u0026#34;gorm.io/driver/mysql\u0026#34; \u0026#34;gorm.io/gorm\u0026#34; ) func TestEmailService_SendRegistrationEmail(t *testing.T) { db, mock, err := sqlmock.New() assert.NoError(t, err) dialector := mysql.New(mysql.Config{ DSN: \u0026#34;dummy\u0026#34;, DriverName: \u0026#34;mysql\u0026#34;, Conn: db, }) finalDB, err := gorm.Open(dialector, \u0026amp;gorm.Config{}) repository := infrastructure.NewUserRepository(finalDB) service := NewEmailService(repository) // // a lot of code to define mocked SQL queries // // and then actual test } In contrast to some languages, like PHP, we cannot simply mock whatever we would like in Go. Mocking in Go relies on the usage of interfaces, for which we can define a mocked implementation, but we cannot do the same for structs. Therefore, we cannot mock UserRepository, as it is a struct. In such a case, we need to create a mock on the lower level, in this case, on the Gorm connection object, which we can achieve using the SQLMock package.\nHowever, even with this approach, it is neither reliable nor efficient for testing. We need to mock too many SQL queries and have extensive knowledge about the database schema. Any change inside the database requires us to adapt unit tests. Apart from unit testing issues, we face an even bigger problem. What will happen if we decide to switch the storage to something else, like Cassandra, especially if we plan to have a distributed storage system for customers in the future? In such a scenario, if we continue using this implementation of UserRepository, it will lead to numerous refactorings. Now, we can see the implications of a high-level component depending on a low-level one. But what about abstractions that rely on details?\nLet\u0026rsquo;s check the code below:\nUserRepository interface\ntype User struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` // some fields } type UserRepository interface { GetByID(id uint) (*User, error) } To address the first issue with high and low-level components, we should start by defining some interfaces. In this case, we can define UserRepository as an interface on the domain layer. This step allows us to decouple EmailService from the database to some extent, but not entirely. Take a look at the User struct; it still contains a definition for mapping to the database. Even though such a struct resides in the domain layer, it retains infrastructural details. Our new interface UserRepository (abstraction) still depends on the User struct with the database schema (details), which means we are still breaking the Dependency Inversion Principle (DIP). Changing the database schema will inevitably lead to changes in our interface. This interface may still use the same User struct, but it will carry changes from a low-level layer.\nIn the end, with this refactoring, we haven\u0026rsquo;t achieved much. We are still in the wrong position, and this has several consequences:\nWe cannot effectively test our business or application logic. Any change to the database engine or table structure affects our highest levels. We cannot easily switch to a different type of storage. Our model is strongly coupled to the storage layer. So, once again, let\u0026rsquo;s refactor this piece of code.\nHow we do respect The Dependency Inversion # High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.\nLet\u0026rsquo;s revisit the original directive for The Dependency Inversion Principle and focus on the bold sentences. They provide us with some guidance for the refactoring process. We need to define an abstraction (an interface) that both of our components, EmailService and UserRepository, will depend on. This abstraction should not be tied to any technical details, such as the Gorm object.\nLet\u0026rsquo;s take a look at the following code:\nThe Infrastructure Layer\ntype UserGorm struct { // some fields } func (g UserGorm) ToUser() *domain.User { return \u0026amp;domain.User{ // some fields } } type UserDatabaseRepository struct { db *gorm.DB } var _ domain.UserRepository = \u0026amp;UserDatabaseRepository{} func NewUserDatabaseRepository(db *gorm.DB) UserRepository { return \u0026amp;UserDatabaseRepository{ db: db, } } func (r *UserDatabaseRepository) GetByID(id uint) (*domain.User, error) { user := UserGorm{} err := r.db.Where(\u0026#34;id = ?\u0026#34;, id).First(\u0026amp;user).Error if err != nil { return nil, err } return user.ToUser(), nil } In the new code structure, we observe the UserRepository interface as a component that relies on the User struct, both of which reside within the domain layer. The User struct no longer directly reflects the database schema; instead, we use the UserGorm struct for this purpose, which belongs to the infrastructure layer. The UserGorm struct provides a method called ToUser, which facilitates the mapping to the actual User struct.\nThe Domain Layer\ntype User struct { // some fields } type UserRepository interface { GetByID(id uint) (*User, error) } In this setup, UserGorm serves as part of the implementation details within UserDatabaseRepository, which acts as the concrete implementation for UserRepository. Within the domain and application layers, our dependencies are exclusively on the UserRepository interface and the User Entities, both originating from the domain layer. Within the infrastructure layer, we can define as many implementations for UserRepository as needed, such as UserFileRepository or UserCassandraRepository.\nThe Application Layer\ntype EmailService struct { repository domain.UserRepository // some email sender } func NewEmailService(repository domain.UserRepository) *EmailService { return \u0026amp;EmailService{ repository: repository, } } func (s *EmailService) SendRegistrationEmail(userID uint) error { user, err := s.repository.GetByID(userID) if err != nil { return err } // send email return nil } The high-level component (EmailService) depends on an abstraction, as it contains a field with the type UserRepository. Now, let\u0026rsquo;s explore how the low-level component depends on this abstraction.\nIn Go, structs implicitly implement interfaces, so there\u0026rsquo;s no need to explicitly add code indicating that UserDatabaseRepository implements UserRepository. However, we can include a check with a blank identifier to ensure this relationship. This approach allows us to have better control over our dependencies. Our structs depend on interfaces, and if we ever need to change our dependencies, we can define different implementations and inject them. This technique aligns with the Dependency Injection pattern, a common practice in various frameworks.\nIn Go, several DI libraries are available, such as the one from Facebook, Wire, or Dingo.\nNow, let\u0026rsquo;s examine how this refactoring affects our unit testing.\nUnit Tests for EmailService\nimport ( \u0026#34;errors\u0026#34; \u0026#34;testing\u0026#34; ) type GetByIDFunc func(id uint) (*User, error) func (f GetByIDFunc) GetByID(id uint) (*User, error) { return f(id) } func TestEmailService_SendRegistrationEmail(t *testing.T) { service := NewEmailService(GetByIDFunc(func(id uint) (*User, error) { return nil, errors.New(\u0026#34;error\u0026#34;) })) // // and just to call the service } Following this refactoring, we can easily create a straightforward mock using a new type, GetByIDFunc. This type defines a function signature that matches the GetByID method of the UserRepository interface. In Go, it\u0026rsquo;s a common practice to define a function type and assign a method to it in order to implement an interface. This approach greatly improves the elegance and efficiency of our testing process. We now have the flexibility to inject different UserRepository implementations for various use cases and precisely control the test outcomes.\nSome more examples # Breaking the Dependency Inversion Principle (DIP) isn\u0026rsquo;t limited to structs alone; it can also occur with standalone, independent functions. For instance:\nBreaking DIP in Functions\ntype User struct { // some fields } type UserJSON struct { // some fields } func (j UserJSON) ToUser() *User { return \u0026amp;User{ // some fields } } func GetUser(id uint) (*User, error) { filename := fmt.Sprintf(\u0026#34;user_%d.json\u0026#34;, id) data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var user UserJSON err = json.Unmarshal(data, \u0026amp;user) if err != nil { return nil, err } return user.ToUser(), nil } We aim to retrieve data for a User, and for this task, we utilize files in JSON format. The GetUser method reads from a file and converts the file content into a User object. However, this method is tightly coupled with the presence of these files, making it challenging to write effective tests. This is especially true when we introduce additional validation rules to the GetUser method at a later stage. Our code\u0026rsquo;s heavy reliance on specific details creates testing difficulties, emphasizing the need for abstractions:\nRespecting DIP in Functions\ntype User struct { // some fields } type UserJSON struct { // some fields } func (j UserJSON) ToUser() *User { return \u0026amp;User{ // some fields } } func GetUserFile(id uint) (io.Reader, error) { filename := fmt.Sprintf(\u0026#34;user_%d.json\u0026#34;, id) file, err := os.Open(filename) if err != nil { return nil, err } return file, nil } func GetUserHTTP(id uint) (io.Reader, error) { uri := fmt.Sprintf(\u0026#34;http://some-api.com/users/%d\u0026#34;, id) resp, err := http.Get(uri) if err != nil { return nil, err } return resp.Body, nil } func GetDummyUser(userJSON UserJSON) (io.Reader, error) { data, err := json.Marshal(userJSON) if err != nil { return nil, err } return bytes.NewReader(data), nil } func GetUser(reader io.Reader) (*User, error) { data, err := ioutil.ReadAll(reader) if err != nil { return nil, err } var user UserJSON err = json.Unmarshal(data, \u0026amp;user) if err != nil { return nil, err } return user.ToUser(), nil } With this revised implementation, the GetUser method depends on an instance of the Reader interface. This interface is part of the Go core package, IO. Using this approach, we can define various methods that provide implementations for the Reader interface, such as GetUserFile, GetUserHTTP, or GetDummyUser (which is useful for testing the GetUser method). This strategy can be employed in various scenarios to address challenges related to unit testing or dependency cycles in Go. By introducing interfaces and multiple implementations, we can achieve effective decoupling.\nConclusion # The Dependency Inversion Principle is the last SOLID principle, represented by the letter D in the word SOLID. This principle asserts that high-level components should not rely on low-level components. Instead, all our components should be built on abstractions, specifically interfaces. These abstractions enable us to use our code with greater flexibility and to conduct thorough testing.\nUseful Resources # Martin Fowler Clean Code ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-solid-dependency-inversion/","section":"Articles","summary":"Learning a new programming language is often a straightforward process. I often hear: “The first programming language you learn in a year. The second one in a month. The third one in a week, and then each next one in a day.” Saying that is an exaggeration, but it is not too distant from the truth in some cases. For example, jumping to a language relatively similar to the previous one, like Java and C#, can be a straightforward process. But sometimes, switching is tricky, even when we switch from one Object-Oriented language to another. Many features influence such transitions, like strong or weak types, if a language has interfaces, abstract classes, or classes at all. Some of those difficulties we experience immediately after switching, and we adopt a new approach. But some issues we experience later, during unit testing, for example. And then, we learn why The Dependency Inversion Principle is essential, especially in Go.\nWhen we do not respect The Dependency Inversion # High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.\nAbove is the definition of DIP as presented by Uncle Bob in his paper. There are also more details inside his blog. So, how can we understand this, especially in the context of Go? First, we should accept Abstraction as an object-oriented programming concept. We use this concept to expose essential behaviors and hide the details of their implementation.\nSecond, what are high and low-level modules? In the context of Go, high-level modules are software components used at the top of the application, such as code used for presentation. It can also be code close to the top level, like code for business logic or some use-case components. It is essential to understand it as a layer that provides real business value to our application. On the other hand, low-level software components are mostly small code pieces that support the higher level. They hide technical details about different infrastructural integrations. For example, this could be a struct that contains the logic for retrieving data from the database, sending an SQS message, fetching a value from Redis, or sending an HTTP request to an external API. So, what does it look like when we break The Dependency Inversion Principle, and our high-level component depends on one low-level component?\n","title":"Practical SOLID in Golang: Dependency Inversion Principle","type":"article"},{"content":"","date":"18 September 2023","externalUrl":null,"permalink":"/tags/solid/","section":"Tags","summary":"","title":"Solid","type":"tags"},{"content":"","date":"18 September 2023","externalUrl":null,"permalink":"/series/solid-principles-in-golang/","section":"Series","summary":"","title":"SOLID Principles in Golang","type":"series"},{"content":"When beginners embark on their programming journey, the initial focus is typically on algorithms and adapting to a new way of thinking. After some time, they delve into Object-Oriented Programming (OOP). If this transition is delayed, it can be challenging to shift from a functional programming mindset. However, eventually, they embrace the use of objects and incorporate them into their code where necessary, sometimes even where they\u0026rsquo;re not needed. As they learn about abstractions and strive to make their code more reusable, they may overgeneralize, resulting in abstractions applied everywhere, which can hinder future development. At some point, they come to realize the importance of setting boundaries for excessive generalization. Fortunately, The Interface Segregation Principle has already provided a guideline for this, representing the \u0026ldquo;I\u0026rdquo; in the word SOLID.\nWhen we do not respect The Interface Segregation # Maintain small interfaces to prevent users from relying on unnecessary features.\nUncle Bob introduced this principle, and you can find more details about it on his blog. This principle clearly states its requirement, perhaps better than any other SOLID principle. Its straightforward advice to keep interfaces as small as possible should not be interpreted as merely advocating one-method interfaces. Instead, we should consider the cohesion of features that an interface encompasses.\nLet\u0026rsquo;s analyze the code below:\nUser interface\ntype User interface { AddToShoppingCart(product Product) IsLoggedIn() bool Pay(money Money) error HasPremium() bool HasDiscountFor(product Product) bool // // some additional methods // } Let\u0026rsquo;s assume we want to create an application for shopping. One approach is to define an interface User, as demonstrated in the code example. This interface includes various features that a user can have. On our platform, a User can add a Product to the ShoppingCart, make a purchase, and receive discounts on specific Products. However, the challenge is that only specific types of Users can perform all of these actions.\nGuest struct\ntype Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } func (g *Guest) IsLoggedIn() bool { return false } func (g *Guest) Pay(Money) error { return errors.New(\u0026#34;user is not logged in\u0026#34;) } func (g *Guest) HasPremium() bool { return false } func (g *Guest) HasDiscountFor(Product) bool { return false } We have implemented this interface with three structs. The first one is the Guest struct, representing a user who is not logged in but can still add a Product to the ShoppingCart. The second implementation is the NormalCustomer, which can do everything a Guest can, plus make a purchase. The third implementation is the PremiumCustomer, which can use all features of our system.\nNormalCustomer struct\ntype NormalCustomer struct { cart ShoppingCart wallet Wallet // // some additional fields // } func (c *NormalCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *NormalCustomer) IsLoggedIn() bool { return true } func (c *NormalCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *NormalCustomer) HasPremium() bool { return false } func (c *NormalCustomer) HasDiscountFor(Product) bool { return false } PremiumCustomer struct\ntype PremiumCustomer struct { cart ShoppingCart wallet Wallet policies []DiscountPolicy // // some additional fields // } func (c *PremiumCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *PremiumCustomer) IsLoggedIn() bool { return true } func (c *PremiumCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *PremiumCustomer) HasPremium() bool { return true } func (c *PremiumCustomer) HasDiscountFor(product Product) bool { for _, p := range c.policies { if p.IsApplicableFor(c, product) { return true } } return false } Now, take a closer look at all three structs. Only the PremiumCustomer requires all three methods. Perhaps we could assign all of them to the NormalCustomer, but definitely, we hardly need more than two methods for the Guest. Methods like HasPremium and HasDiscountFor don\u0026rsquo;t make sense for a Guest. If this struct represents a User who is not logged in, why would we even consider implementing methods for discounts? In such cases, we might even call the panic method with the error message \u0026ldquo;method is not implemented\u0026rdquo; — that would be more honest in this code. In a typical scenario, we shouldn\u0026rsquo;t even call the HasPremium method from a Guest.\nUserService struct\ntype UserService struct { // // some fields // } func (u *UserService) Checkout(ctx context.Context, user User, product Product) error { if !user.IsLoggedIn() { return errors.New(\u0026#34;user is not logged in\u0026#34;)\t} var money Money // // some calculation // if user.HasDiscountFor(product) { // // apply discount // } return user.Pay(money) } All of this complexity was introduced to add generalization inside the UserService to handle all types of Users in the same place, using the same code. However, as a result, we now have:\nMany structs with unused methods. Methods that need to be marked to prevent their use. Additional code for unit testing. Unnatural polymorphism. So, let\u0026rsquo;s refactor this situation to improve it.\nHow we do respect The Interface Segregation # Build interfaces around the minimal cohesive group of features.\nWe don\u0026rsquo;t need to overcomplicate things; all we have to do is define a minimal interface that provides a complete set of features. Let\u0026rsquo;s take a look at the following code:\nUser interfaces\ntype User interface { AddToShoppingCart(product Product) // // some additional methods // } type LoggedInUser interface { User Pay(money Money) error // // some additional methods // } type PremiumUser interface { LoggedInUser HasDiscountFor(product Product) bool // // some additional methods // } Now, instead of one interface, we have three: PremiumUser embeds LoggedInUser, which embeds User. Additionally, each of them introduces one method. The User interface now represents only customers who are still not authenticated on our platform. For such types, we know they can use features of the ShoppingCart. The new LoggedInUser interface represents all our authenticated customers, and the PremiumUser interface represents all authenticated customers with a paid premium account.\nConcrete User implementations\ntype Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } type NormalCustomer struct { cart ShoppingCart wallet Wallet // // some additional fields // } func (c *NormalCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *NormalCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } type PremiumCustomer struct { cart ShoppingCart wallet Wallet policies []DiscountPolicy // // some additional fields // } func (c *PremiumCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *PremiumCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *PremiumCustomer) HasDiscountFor(product Product) bool { for _, p := range c.policies { if p.IsApplicableFor(c, product) { return true } } return false } Notice this: we indeed added two more interfaces, but we removed two methods: IsLoggedIn and HasPremium. Those methods are not part of our interface signature. But how can we work without them?\nUserService struct\ntype UserService struct { // // some fields // } func (u *UserService) Checkout(ctx context.Context, user User, product Product) error { loggedIn, ok := user.(LoggedInUser) if !ok { return errors.New(\u0026#34;user is not logged in\u0026#34;) } var money Money // // some calculation // if premium, ok := loggedIn.(PremiumUser); ok \u0026amp;\u0026amp; premium.HasDiscountFor(product) { // // apply discount // } return loggedIn.Pay(money) } As you can see in the UserService, instead of using methods with boolean results, we just clarify the subtype of the User interface. If User implements LoggedInUser, we know that we are dealing with an authenticated customer. Also, if User implements PremiumUser, we know that we are dealing with a customer with a premium account. So, by casting, we can already check for some business rules. Besides those two methods, all structs from before are now more lightweight. Instead of each of them having five methods, where many of them are not used at all, now they only have methods they really need.\nSome more examples # Although it is always good to create small and flexible interfaces, we should introduce them with their purpose in mind. Adding small interfaces to simplify them but still implementing them together in the same struct does not make too much sense.\nLet us examine the example below:\nToo much splitting\ntype UserWithFirstName interface { FirstName() string } type UserWithLastName interface { LastName() string } type UserWithFullName interface { FullName() string } type UserWithDiscount interface { HasDiscountFor(product Product) bool } Optimal splitting\ntype UserWithName interface { FirstName() string LastName() string FullName() string } type UserWithDiscount interface { UserWithName HasDiscountFor(product Product) bool } In this case, we\u0026rsquo;ve split the interface too finely. While one-method interfaces can be useful in some situations, it doesn\u0026rsquo;t make sense here. If a customer is registered on our platform, they will need to provide both their first and last name for billing purposes. So, our User interface should include both the FirstName and LastName methods, and naturally, FullName as well.\nSplitting these three methods into three separate interfaces doesn\u0026rsquo;t make sense, as these methods are closely related and always go together. This isn\u0026rsquo;t the right example for one-method interfaces.\nBut what would be a good example?\nExample from IO package\npackage io type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } type Seeker interface { Seek(offset int64, whence int) (int64, error) } type WriteCloser interface { Writer Closer } type ReadWriteCloser interface { Reader Writer Closer } //.... and so on The perfect example in Go is the IO package. It provides many codes and interfaces for handling I/O operations, and probably all Go developers have used this package at least once. It provides interfaces such as Reader, Writer, Closer, and Seeker. Each of them defines only one method: Read, Write, Close, and Seek, respectively. We use all of these interfaces to read, write, seek within a slice of bytes, and close a particular source. To have more flexibility with different sources, all functionalities are placed in these interfaces. Later, they can be used to build more complex interfaces, like WriteCloser, ReadWriteCloser, and so on.\nConclusion # The Interface Segregation Principle is the fourth SOLID principle, represented by the letter \u0026ldquo;I\u0026rdquo; in the word SOLID. It teaches us to keep our interfaces as small as possible. When we need to accommodate various types, we should shield them with distinct interfaces. However, we should also refrain from creating excessively small interfaces and ensure they offer complete functionality.\nUseful Resources # Martin Fowler Clean Code ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-solid-interface-segregation/","section":"Articles","summary":"When beginners embark on their programming journey, the initial focus is typically on algorithms and adapting to a new way of thinking. After some time, they delve into Object-Oriented Programming (OOP). If this transition is delayed, it can be challenging to shift from a functional programming mindset. However, eventually, they embrace the use of objects and incorporate them into their code where necessary, sometimes even where they’re not needed. As they learn about abstractions and strive to make their code more reusable, they may overgeneralize, resulting in abstractions applied everywhere, which can hinder future development. At some point, they come to realize the importance of setting boundaries for excessive generalization. Fortunately, The Interface Segregation Principle has already provided a guideline for this, representing the “I” in the word SOLID.\nWhen we do not respect The Interface Segregation # Maintain small interfaces to prevent users from relying on unnecessary features.\nUncle Bob introduced this principle, and you can find more details about it on his blog. This principle clearly states its requirement, perhaps better than any other SOLID principle. Its straightforward advice to keep interfaces as small as possible should not be interpreted as merely advocating one-method interfaces. Instead, we should consider the cohesion of features that an interface encompasses.\nLet’s analyze the code below:\nUser interface\ntype User interface { AddToShoppingCart(product Product) IsLoggedIn() bool Pay(money Money) error HasPremium() bool HasDiscountFor(product Product) bool // // some additional methods // } Let’s assume we want to create an application for shopping. One approach is to define an interface User, as demonstrated in the code example. This interface includes various features that a user can have. On our platform, a User can add a Product to the ShoppingCart, make a purchase, and receive discounts on specific Products. However, the challenge is that only specific types of Users can perform all of these actions.\nGuest struct\ntype Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } func (g *Guest) IsLoggedIn() bool { return false } func (g *Guest) Pay(Money) error { return errors.New(\"user is not logged in\") } func (g *Guest) HasPremium() bool { return false } func (g *Guest) HasDiscountFor(Product) bool { return false } We have implemented this interface with three structs. The first one is the Guest struct, representing a user who is not logged in but can still add a Product to the ShoppingCart. The second implementation is the NormalCustomer, which can do everything a Guest can, plus make a purchase. The third implementation is the PremiumCustomer, which can use all features of our system.\n","title":"Practical SOLID in Golang: Interface Segregation Principle","type":"article"},{"content":"I\u0026rsquo;m not really a fan of reading. Often, when I do read, I find myself losing track of the text\u0026rsquo;s topic for the past few minutes. Many times, I\u0026rsquo;ll go through an entire chapter without really grasping what it was all about in the end. It can be frustrating when I\u0026rsquo;m trying to focus on the content, but I keep realizing I need to backtrack. That\u0026rsquo;s when I turn to various types of media to learn about a topic. The first time I encountered this reading issue was with the SOLID principle, specifically the Liskov Substitution Principle. Its definition was (and still is) too complicated for my taste, especially in its formal format. As you can guess, LSP represents the letter \u0026ldquo;L\u0026rdquo; in the word SOLID. It\u0026rsquo;s not difficult to understand, although a less mathematical definition would be appreciated.\nWhen we do not respect The Liskov Substitution # The first time we encountered this principle was in 1988, thanks to Barbara Liskov. Later, Uncle Bob shared his perspective on this topic in a paper and eventually included it as one of the SOLID principles. Let\u0026rsquo;s take a look at what it says:\nLet Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.\nWell, good luck with that definition.\nNo, seriously, what kind of definition is this? Even as I write this article, I\u0026rsquo;m still struggling to fully grasp this definition, despite my fundamental understanding of LSP. Let\u0026rsquo;s give it another shot:\nIf S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.\nOkay, this is a bit clearer now. If ObjectA is an instance of ClassA, and ObjectB is an instance of ClassB, and ClassB is a subtype of ClassA – if we use ObjectB instead of ObjectA somewhere in the code, the application\u0026rsquo;s functionality must not break. We\u0026rsquo;re talking about classes and inheritance here, two concepts that aren\u0026rsquo;t prominent in Go. However, we can still apply this principle using interfaces and polymorphism.\nWrong implementation of Update method\ntype User struct { ID uuid.UUID // // some fields // } type UserRepository interface { Update(ctx context.Context, user User) error } type DBUserRepository struct { db *gorm.DB } func (r *DBUserRepository) Update(ctx context.Context, user User) error { return r.db.WithContext(ctx).Delete(user).Error } In this code example, we can see one that\u0026rsquo;s quite absurd and far from best practices. Instead of updating the User in the database, as the Update method claims, it actually deletes it. But that\u0026rsquo;s precisely the point here. We have an interface, UserRepository, followed by a struct, DBUserRepository. While this struct technically implements the interface, it completely diverges from what the interface is supposed to do. In fact, it breaks the functionality of the interface rather than fulfilling its expectations. This highlights the essence of the Liskov Substitution Principle (LSP) in Go: a struct must not violate the intended behavior of the interface.\nNow, let\u0026rsquo;s explore some less ridiculous examples:\nMultiple implementations of UserRepository\ntype UserRepository interface { Create(ctx context.Context, user User) (*User, error) Update(ctx context.Context, user User) error } type DBUserRepository struct { db *gorm.DB } func (r *DBUserRepository) Create(ctx context.Context, user User) (*User, error) { err := r.db.WithContext(ctx).Create(\u0026amp;user).Error return \u0026amp;user, err } func (r *DBUserRepository) Update(ctx context.Context, user User) error { return r.db.WithContext(ctx).Save(\u0026amp;user).Error } type MemoryUserRepository struct { users map[uuid.UUID]User } func (r *MemoryUserRepository) Create(_ context.Context, user User) (*User, error) { if r.users == nil { r.users = map[uuid.UUID]User{} } user.ID = uuid.New() r.users[user.ID] = user return \u0026amp;user, nil } func (r *MemoryUserRepository) Update(_ context.Context, user User) error { if r.users == nil { r.users = map[uuid.UUID]User{} } r.users[user.ID] = user return nil } In this example, we have a new UserRepository interface and two implementations: DBUserRepository and MemoryUserRepository. As we can observe, MemoryUserRepository includes the Context argument, although it\u0026rsquo;s not actually needed. It\u0026rsquo;s there just to adhere to the interface, and that\u0026rsquo;s where the problem begins. We\u0026rsquo;ve adapted MemoryUserRepository to conform to the interface, even though this adaptation feels unnatural. Consequently, this approach allows us to switch data sources in our application, where one source is not a permanent storage solution.\nThe issue here is that the Repository pattern is intended to represent an interface to the underlying permanent data storage, such as a database. It should not double as a caching system, as in the case where we store Users in memory. Unnatural implementations like this one can have consequences not only in terms of semantics but also in the actual code. Such situations are more apparent during implementation and challenging to rectify, often requiring significant refactoring.\nTo illustrate this case, we can examine the famous example involving geometrical shapes. Interestingly, this example contradicts geometric principles.\nGeometrical problem\ntype ConvexQuadrilateral interface { GetArea() int } type Rectangle interface { ConvexQuadrilateral SetA(a int) SetB(b int) } type Oblong struct { Rectangle a int b int } func (o *Oblong) SetA(a int) { o.a = a } func (o *Oblong) SetB(b int) { o.b = b } func (o Oblong) GetArea() int { return o.a * o.b } type Square struct { Rectangle a int } func (o *Square) SetA(a int) { o.a = a } func (o Square) GetArea() int { return o.a * o.a } func (o *Square) SetB(b int) { // // should it be o.a = b ? // or should it be empty? // } In the example above, we can see the implementation of geometrical shapes in Go. In geometry, we can establish subtyping relationships among convex quadrilaterals, rectangles, oblongs, and squares. When translating this concept into Go code for implementing area calculation logic, we may end up with something similar to what we see here.\nAt the top, we have an interface called ConvexQuadrilateral, which defines only one method, GetArea. As a subtype of ConvexQuadrilateral, we define an interface called Rectangle. This subtype includes two methods, SetA and SetB, as rectangles have two sides relevant to their area.\nNext, we have the actual implementations. The first one is Oblong, which can have either a wider width or a wider height. In geometry, it refers to any rectangle that is not a square. Implementing the logic for this struct is straightforward.\nThe second subtype of Rectangle is Square. In geometry, a square is considered a subtype of a rectangle. However, if we follow this subtyping relationship in software development, we encounter an issue. A square has all four sides equal, making the SetB method obsolete. To adhere to the initial subtyping structure we chose, we end up with obsolete methods in our code. The same issue arises if we opt for a slightly different approach:\nAnother Geometrical problem\ntype ConvexQuadrilateral interface { GetArea() int } type EquilateralRectangle interface { ConvexQuadrilateral SetA(a int) } type Oblong struct { EquilateralRectangle a int b int } func (o *Oblong) SetA(a int) { o.a = a } func (o *Oblong) SetB(b int) { // where is this method defined? o.b = b } func (o Oblong) GetArea() int { return o.a * o.b } type Square struct { EquilateralRectangle a int } func (o *Square) SetA(a int) { o.a = a } func (o Square) GetArea() int { return o.a * o.a } In the example above, instead of using the Rectangle interface, we introduced the EquilateralRectangle interface. In geometry, this interface represents a rectangle with all four sides equal. In this case, by defining only the SetA method in our interface, we avoid introducing obsolete code in our implementation. However, this approach still breaks the Liskov Substitution Principle because we introduced an additional method, SetB, for the Oblong type, which is necessary to calculate the area, even though our interface implies otherwise.\nNow that we\u0026rsquo;ve started grasping the concept of The Liskov Substitution Principle in Go, let\u0026rsquo;s summarize what can go wrong if we violate it:\nIt provides a false shortcut for implementation. It can lead to obsolete code. It can disrupt the expected code execution. It can undermine the intended use case. It can result in an unmaintainable interface structure. So, once again, let\u0026rsquo;s proceed with some refactoring.\nHow we do respect The Liskov Substitution # We can achieve subtyping in Go through interfaces by ensuring that each implementation adheres to the interface\u0026rsquo;s purpose and methods.\nI won\u0026rsquo;t provide the corrected implementation for the first example we encountered, as the issue is quite obvious: the Update method should update the User, not delete it. Instead, let\u0026rsquo;s focus on resolving the problem with different implementations of the UserRepository interface:\nRepositories and Caches\ntype UserRepository interface { Create(ctx context.Context, user User) (*User, error) Update(ctx context.Context, user User) error } type MySQLUserRepository struct { db *gorm.DB } type CassandraUserRepository struct { session *gocql.Session } type UserCache interface { Create(user User) Update(user User) } type MemoryUserCache struct { users map[uuid.UUID]User } In this example, we have divided the interface into two separate interfaces, each with distinct purposes and method signatures. We now have the UserRepository interface, which is dedicated to permanently storing user data in some storage. To fulfill this purpose, we have provided concrete implementations such as MySQLUserRepository and CassandraUserRepository.\nOn the other hand, we introduced the UserCache interface, which serves the specific function of temporarily caching user data. As a concrete implementation of UserCache, we can utilize MemoryUserCache. Now, let\u0026rsquo;s explore a more intricate scenario in the geometrical example:\nSolving Geometrical problem\ntype ConvexQuadrilateral interface { GetArea() int } type EquilateralQuadrilateral interface { ConvexQuadrilateral SetA(a int) } type NonEquilateralQuadrilateral interface { ConvexQuadrilateral SetA(a int) SetB(b int) } type NonEquiangularQuadrilateral interface { ConvexQuadrilateral SetAngle(angle float64) } type Oblong struct { NonEquilateralQuadrilateral a int b int } type Square struct { EquilateralQuadrilateral a int } type Parallelogram struct { NonEquilateralQuadrilateral NonEquiangularQuadrilateral a int b int angle float64 } type Rhombus struct { EquilateralQuadrilateral NonEquiangularQuadrilateral a int angle float64 } To support subtyping for geometrical shapes in Go, it\u0026rsquo;s crucial to consider all of their features to avoid broken or obsolete methods. In this case, we introduced three new interfaces: EquilateralQuadrilateral (representing a quadrilateral with all four equal sides), NonEquilateralQuadrilateral (representing a quadrilateral with two pairs of equal sides), and NonEquiangularQuadrilateral (representing a quadrilateral with two pairs of equal angles). Each of these interfaces provides additional methods necessary to supply the required data for area calculation.\nNow, we can define a Square interface with only the SetA method, an Oblong interface with both SetA and SetB methods, and a Parallelogram interface with all these methods plus SetAngle. In this approach, we didn\u0026rsquo;t strictly adhere to subtyping but focused on including necessary features. With these fixed examples, we\u0026rsquo;ve restructured our code to consistently meet end-user expectations. This also eliminates obsolete methods without breaking any existing ones, resulting in stable code.\nConclusion # The Liskov Substitution Principle teaches us the correct way to apply subtyping. We should avoid forced polymorphism, even if it mimics real-world situations. The LSP represents the letter L in the word SOLID. While it is typically associated with inheritance and classes, which are not supported in Go, we can still apply this principle to achieve polymorphism and interfaces.\nUseful Resources # Martin Fowler Clean Code ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-solid-liskov-substitution/","section":"Articles","summary":"I’m not really a fan of reading. Often, when I do read, I find myself losing track of the text’s topic for the past few minutes. Many times, I’ll go through an entire chapter without really grasping what it was all about in the end. It can be frustrating when I’m trying to focus on the content, but I keep realizing I need to backtrack. That’s when I turn to various types of media to learn about a topic. The first time I encountered this reading issue was with the SOLID principle, specifically the Liskov Substitution Principle. Its definition was (and still is) too complicated for my taste, especially in its formal format. As you can guess, LSP represents the letter “L” in the word SOLID. It’s not difficult to understand, although a less mathematical definition would be appreciated.\nWhen we do not respect The Liskov Substitution # The first time we encountered this principle was in 1988, thanks to Barbara Liskov. Later, Uncle Bob shared his perspective on this topic in a paper and eventually included it as one of the SOLID principles. Let’s take a look at what it says:\nLet Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.\nWell, good luck with that definition.\nNo, seriously, what kind of definition is this? Even as I write this article, I’m still struggling to fully grasp this definition, despite my fundamental understanding of LSP. Let’s give it another shot:\nIf S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.\nOkay, this is a bit clearer now. If ObjectA is an instance of ClassA, and ObjectB is an instance of ClassB, and ClassB is a subtype of ClassA – if we use ObjectB instead of ObjectA somewhere in the code, the application’s functionality must not break. We’re talking about classes and inheritance here, two concepts that aren’t prominent in Go. However, we can still apply this principle using interfaces and polymorphism.\nWrong implementation of Update method\ntype User struct { ID uuid.UUID // // some fields // } type UserRepository interface { Update(ctx context.Context, user User) error } type DBUserRepository struct { db *gorm.DB } func (r *DBUserRepository) Update(ctx context.Context, user User) error { return r.db.WithContext(ctx).Delete(user).Error } In this code example, we can see one that’s quite absurd and far from best practices. Instead of updating the User in the database, as the Update method claims, it actually deletes it. But that’s precisely the point here. We have an interface, UserRepository, followed by a struct, DBUserRepository. While this struct technically implements the interface, it completely diverges from what the interface is supposed to do. In fact, it breaks the functionality of the interface rather than fulfilling its expectations. This highlights the essence of the Liskov Substitution Principle (LSP) in Go: a struct must not violate the intended behavior of the interface.\n","title":"Practical SOLID in Golang: Liskov Substitution Principle","type":"article"},{"content":"Many different approaches and principles can lead to long-term improvements in our code. Some of them are well-known in the software development community, while others remain somewhat under the radar. In my opinion, this is the case with The Open/Closed Principle, represented by the letter O in the word SOLID. In my experience, only those genuinely interested in SOLID principles tend to understand what this principle means. We may have applied this principle without even realizing it in some instances, such as when working with the Strategy pattern. However, the Strategy pattern is just one application of the Open/Closed Principle. In this article, we will delve into the full purpose of this principle, with all examples provided in Go.\nWhen we do not respect the Open/Closed Principle # You should be able to extend the behavior of a system without having to modify that system.\nThe requirement for the Open/Closed Principle, as seen above, was provided by Uncle Bob in his blog. I prefer this way of defining The Open/Closed Principle because it highlights its full brilliance. At first glance, it may seem like an absurd requirement. Seriously, how can we extend something without modifying it? I mean, is it possible to change something without changing it? By examining the code example below, we can see what it means for certain structures not to adhere to this principle and the potential consequences.\nThe bad Authentication Service\ntype AuthenticationService struct { // // some fields // } func (s *AuthenticationService) Authenticate(ctx *gin.Context) (*User, error) { switch ctx.GetString(\u0026#34;authType\u0026#34;) { case \u0026#34;bearer\u0026#34;: return c.authenticateWithBearerToken(ctx.Request.Header) case \u0026#34;basic\u0026#34;: return c.authenticateWithBasicAuth(ctx.Request.Header) case \u0026#34;applicationKey\u0026#34;: return c.authenticateWithApplicationKey(ctx.Query(\u0026#34;applicationKey\u0026#34;)) } return nil, errors.New(\u0026#34;unrecognized authentication type\u0026#34;) } func (s *AuthenticationService) authenticateWithApplicationKey(key string) (*User, error) { // // authenticate User from Application Key // } func (s *AuthenticationService) authenticateWithBasicAuth(h http.Header) (*User, error) { // // authenticate User from Basic Auth // } func (s *AuthenticationService) authenticateWithBearerToken(h http.Header) (*User, error) { // // validate JWT token from the request header // } The example presents a single struct, AuthenticationService. Its purpose is to authenticate a User from the web application\u0026rsquo;s Context, supported by the Gin package. Here, we have the main method, Authenticate, which checks for specific authentication type associated with the data within the Context. How User is retrieved from the Context may vary based on whether the User uses a bearer JWT token, basic authentication, or an application key.\nInside the struct, we\u0026rsquo;ve included various methods for extracting permission slices in different ways. If we adhere to The Single Responsibility Principle, AuthenticationService should be responsible for determining if the authentication mean exists within the Context, without being involved in the authorization process itself. The authorization process should be defined elsewhere, possibly in another struct or module. So, if we intend to expand the authorization process elsewhere, we\u0026rsquo;d also need to adjust the logic here.\nThis implementation leads to several issues:\nAuthenticationService mixes logic initially handled in another location. Any changes to the authentication logic, even if it\u0026rsquo;s in a different module, require modifications in AuthenticationService. Adding a new method of extracting an User from Context always necessitates modifications to AuthenticationService. The logic within AuthenticationService inevitably grows with each new authentication method. Unit testing for AuthenticationService involves too many technical details related to different authentication methods. So, once again, we have some code to refactor.\nHow we do respect The Open/Closed Principle # The Open/Closed Principle says that software structures should be open for extension but closed for modification.\nThe statement above suggests potential approaches for our new code, emphasizing the need to adhere to the Open/Closed Principle (OCP). Our code should be designed in a way that enables extensions to be added from external sources. In Object-Oriented Programming, we achieve such extensibility by employing various implementations for the same interface, effectively utilizing polymorphism.\nThe refactored Authentication Service\ntype AuthenticationProvider interface { Type() string Authenticate(ctx *gin.Context) (*User, error) } type AuthenticationService struct { providers []AuthenticationProvider // // some fields // } func (s *AuthenticationService) Authenticate(ctx *gin.Context) (*User, error) { for _, provider := range c.providers { if ctx.GetString(\u0026#34;authType\u0026#34;) != provider.Type() { continue } return provider.Authenticate(ctx) } return nil, errors.New(\u0026#34;unrecognized authentication type\u0026#34;) } In the example above, we have a candidate that adheres to the Open/Closed Principle (OCP). The struct, AuthenticationService, doesn\u0026rsquo;t conceal technical details about extracting a User from the Context. Instead, we introduced a new interface, AuthenticationProvider, which serves as the designated place for implementing various authentication logic. For instance, it can include TokenBearerProvider, ApiKeyProvider, or BasicAuthProvider. This approach allows us to centralize the logic for authorized users within one module, rather than scattering it throughout the codebase. Furthermore, we achieve our primary objective: extending AuthenticationService without needing to modify it. We can initialize AuthenticationService with as many different AuthenticationProviders as required.\nSuppose we want to introduce the capability to obtain a User from a session key. In that case, we create a new SessionProvider, responsible for extracting the cookie from the Context and using it to retrieve User from the SessionStore. We\u0026rsquo;ve made it feasible to extend AuthenticationService whenever necessary, without altering its internal logic. This illustrates the concept of being open to extension while closed for modification.\nSome more examples # We can apply The Open/Closed Principle to methods, not just to structs. An example of this can be seen in the code below:\nBreaking OCP in Functions\nfunc GetCities(sourceType string, source string) ([]City, error) { var data []byte var err error if sourceType == \u0026#34;file\u0026#34; { data, err = ioutil.ReadFile(source) if err != nil { return nil, err } } else if sourceType == \u0026#34;link\u0026#34; { resp, err := http.Get(source) if err != nil { return nil, err } data, err = ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() } var cities []City err = yaml.Unmarshal(data, \u0026amp;cities) if err != nil { return nil, err } return cities, nil } The function GetCities reads the list of cities from some source. That source may be a file or some resource on the Internet. Still, we may want to read data from memory, from Redis, or any other source in the future. So somehow, it would be better to make the process of reading raw data a little more abstract. With that said, we may provide a reading strategy from the outside as a method argument.\nRespecting OCP in Functions\ntype DataReader func(source string) ([]byte, error) func ReadFromFile(fileName string) ([]byte, error) { data, err := ioutil.ReadFile(fileName) if err != nil { return nil, err } return data, nil } func ReadFromLink(link string) ([]byte, error) { resp, err := http.Get(link) if err != nil { return nil, err } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() return data, nil } func GetCities(reader DataReader, source string) ([]City, error) { data, err := reader(source) if err != nil { return nil, err } var cities []City err = yaml.Unmarshal(data, \u0026amp;cities) if err != nil { return nil, err } return cities, nil } As you can see in the solution above, in Go, we can define a new type that embeds a function. Here, we\u0026rsquo;ve created a new type called DataReader, which represents a function type for reading raw data from some source. The ReadFromFile and ReadFromLink methods are actual implementations of the DataReader type. The GetCities method expects an actual implementation of DataReader as an argument, which is then executed inside the function body to obtain raw data. As you can see, the primary purpose of OCP is to provide more flexibility in our code, making it easier for users to extend our libraries without having to modify them directly. Our libraries become more valuable when others can extend them without the need forking, pull requests, or modifications to the original code.\nConclusion # Thank you for the explanation! The Open/Closed Principle (OCP) is indeed a crucial SOLID principle, emphasizing the importance of designing software in a way that allows for extension without modification of existing code structures. It promotes the use of polymorphism and the creation of clear interfaces to enable this extensibility. OCP helps make software more adaptable and maintainable as requirements change and new features are added.\nUseful Resources # Martin Fowler Clean Code ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-solid-open-closed/","section":"Articles","summary":"Many different approaches and principles can lead to long-term improvements in our code. Some of them are well-known in the software development community, while others remain somewhat under the radar. In my opinion, this is the case with The Open/Closed Principle, represented by the letter O in the word SOLID. In my experience, only those genuinely interested in SOLID principles tend to understand what this principle means. We may have applied this principle without even realizing it in some instances, such as when working with the Strategy pattern. However, the Strategy pattern is just one application of the Open/Closed Principle. In this article, we will delve into the full purpose of this principle, with all examples provided in Go.\nWhen we do not respect the Open/Closed Principle # You should be able to extend the behavior of a system without having to modify that system.\nThe requirement for the Open/Closed Principle, as seen above, was provided by Uncle Bob in his blog. I prefer this way of defining The Open/Closed Principle because it highlights its full brilliance. At first glance, it may seem like an absurd requirement. Seriously, how can we extend something without modifying it? I mean, is it possible to change something without changing it? By examining the code example below, we can see what it means for certain structures not to adhere to this principle and the potential consequences.\nThe bad Authentication Service\ntype AuthenticationService struct { // // some fields // } func (s *AuthenticationService) Authenticate(ctx *gin.Context) (*User, error) { switch ctx.GetString(\"authType\") { case \"bearer\": return c.authenticateWithBearerToken(ctx.Request.Header) case \"basic\": return c.authenticateWithBasicAuth(ctx.Request.Header) case \"applicationKey\": return c.authenticateWithApplicationKey(ctx.Query(\"applicationKey\")) } return nil, errors.New(\"unrecognized authentication type\") } func (s *AuthenticationService) authenticateWithApplicationKey(key string) (*User, error) { // // authenticate User from Application Key // } func (s *AuthenticationService) authenticateWithBasicAuth(h http.Header) (*User, error) { // // authenticate User from Basic Auth // } func (s *AuthenticationService) authenticateWithBearerToken(h http.Header) (*User, error) { // // validate JWT token from the request header // } The example presents a single struct, AuthenticationService. Its purpose is to authenticate a User from the web application’s Context, supported by the Gin package. Here, we have the main method, Authenticate, which checks for specific authentication type associated with the data within the Context. How User is retrieved from the Context may vary based on whether the User uses a bearer JWT token, basic authentication, or an application key.\n","title":"Practical SOLID in Golang: Open/Closed Principle","type":"article"},{"content":"There aren\u0026rsquo;t too many opportunities for a breakthrough in software development. They usually arise from either rewiring our logic after initial misunderstandings or filling in gaps in our knowledge. I appreciate that feeling of deeper understanding. It can happen during a coding session, while reading a book or an online article, or even while sitting on a bus. An internal voice follows, saying, \u0026ldquo;Ah, yes, that\u0026rsquo;s how it works.\u0026rdquo;\nSuddenly, all past mistakes seem to have a logical reason, and future requirements take shape. I experienced such a breakthrough with the SOLID principles, which were first introduced in a document by Uncle Bob and later expounded upon in his book, \u0026ldquo;Clean Architecture.\u0026rdquo; In this article, I intend to embark on a journey through all the SOLID principles, providing examples in Go. The first principle on the list, representing the letter \u0026lsquo;S\u0026rsquo; in SOLID, is the Single Responsibility Principle.\nWhen we do not respect Single Responsibility # The Single Responsibility Principle (SRP) asserts that each software module should serve a single, specific purpose that could lead to change.\nThe sentence above comes directly from Uncle Bob himself. Initially, its application was linked to modules and the practice of segregating responsibilities based on the organization\u0026rsquo;s daily tasks. Nowadays, SRP has a broader scope, influencing various aspects of software development. We can apply its principles to classes, functions, modules, and naturally, in Go, even to structs.\nSome Frankenstein of EmailService\ntype EmailService struct { db *gorm.DB smtpHost string smtpPassword string smtpPort int } func NewEmailService(db *gorm.DB, smtpHost string, smtpPassword string, smtpPort int) *EmailService { return \u0026amp;EmailService{ db: db, smtpHost: smtpHost, smtpPassword: smtpPassword, smtpPort: smtpPort, } } func (s *EmailService) Send(from string, to string, subject string, message string) error { email := EmailGorm{ From: from, To: to, Subject: subject, Message: message, } err := s.db.Create(\u0026amp;email).Error if err != nil { log.Println(err) return err } auth := smtp.PlainAuth(\u0026#34;\u0026#34;, from, s.smtpPassword, s.smtpHost) server := fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, s.smtpHost, s.smtpPort) err = smtp.SendMail(server, auth, from, []string{to}, []byte(message)) if err != nil { log.Println(err) return err } return nil } Let\u0026rsquo;s analyze the code block above. In this code, we have a struct called EmailService, which contains only one method, Send. This service is intended for sending emails. Although it may seem okay at first glance, upon closer inspection, we realize that this code violates the Single Responsibility Principle (SRP) in several ways.\nThe responsibility of the EmailService is not limited to sending emails; it also involves storing an email message in the database and sending it via the SMTP protocol. Pay attention to the sentence above where the word \u0026ldquo;and\u0026rdquo; is emphasized. Using such an expression suggests that we are describing more than one responsibility. When describing the responsibility of a code struct necessitates the use of the word \u0026ldquo;and\u0026rdquo;, it already indicates a violation of the Single Responsibility Principle.\nIn our example, SRP is violated on multiple code levels. First, at the function level, the Send function is responsible for both storing a message in the database and sending an email via the SMTP protocol. Second, at the struct level, EmailService also carries two responsibilities: database storage and email sending.\nWhat are the consequences of such code?\nWhen we need to change the table structure or the type of storage, we must modify the code for sending emails via SMTP. If we decide to integrate with different email service providers like Mailgun or Mailjet, we must alter the code responsible for storing data in the MySQL database. If we opt for various email integration methods within the application, each integration needs to implement logic for database storage. If we divide the application\u0026rsquo;s responsibilities into two teams, one for managing the database and the other for integrating email providers, they will need to work on the same code. Writing unit tests for this service becomes challenging, making it practically untestable. So, let\u0026rsquo;s proceed to refactor this code.\nHow we do respect Single Responsibility # To separate the responsibilities and ensure that each code block has only one reason to exist, we should create a distinct struct for each responsibility. This entails having a separate struct for storing data in a storage system and another struct for sending emails through email service providers. Here\u0026rsquo;s the updated code block:\nEmailRepository\ntype EmailGorm struct { gorm.Model From string To string Subject string Message string } type EmailRepository interface { Save(from string, to string, subject string, message string) error } type EmailDBRepository struct { db *gorm.DB } func NewEmailRepository(db *gorm.DB) EmailRepository { return \u0026amp;EmailDBRepository{ db: db, } } func (r *EmailDBRepository) Save(from string, to string, subject string, message string) error { email := EmailGorm{ From: from, To: to, Subject: subject, Message: message, } err := r.db.Create(\u0026amp;email).Error if err != nil { log.Println(err) return err } return nil } EmailSender\ntype EmailSender interface { Send(from string, to string, subject string, message string) error } type EmailSMTPSender struct { smtpHost string smtpPassword string smtpPort int } func NewEmailSender(smtpHost string, smtpPassword string, smtpPort int) EmailSender { return \u0026amp;EmailSMTPSender{ smtpHost: smtpHost, smtpPassword: smtpPassword, smtpPort: smtpPort, } } func (s *EmailSMTPSender) Send(from string, to string, subject string, message string) error { auth := smtp.PlainAuth(\u0026#34;\u0026#34;, from, s.smtpPassword, s.smtpHost) server := fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, s.smtpHost, s.smtpPort) err := smtp.SendMail(server, auth, from, []string{to}, []byte(message)) if err != nil { log.Println(err) return err } return nil } EmailService\ntype EmailService struct { repository EmailRepository sender EmailSender } func NewEmailService(repository EmailRepository, sender EmailSender) *EmailService { return \u0026amp;EmailService{ repository: repository, sender: sender, } } func (s *EmailService) Send(from string, to string, subject string, message string) error { err := s.repository.Save(from, to, subject, message) if err != nil { return err } return s.sender.Send(from, to, subject, message) } Here, we introduce two new structs. The first one is EmailDBRepository, which serves as an implementation for the EmailRepository interface. It is responsible for persisting data in the underlying database. The second structure is EmailSMTPSender, implementing the EmailSender interface, and exclusively handling email sending over the SMTP protocol.\nNow, you might wonder if EmailService still carries multiple responsibilities since it appears to involve both storing and sending emails. Have we merely abstracted the responsibilities without actually eliminating them? In this context, that is not the case. EmailService no longer bears the responsibility of storing and sending emails itself. Instead, it delegates these tasks to the underlying structs. Its sole responsibility is to forward email processing requests to the appropriate services. There is a clear distinction between holding and delegating responsibility. If removing a specific piece of code would render an entire responsibility meaningless, it\u0026rsquo;s a case of holding. However, if the responsibility remains intact even after removing certain code, it\u0026rsquo;s a matter of delegation. If we were to remove EmailService entirely, we would still have code responsible for storing data in a database and sending emails over SMTP. Therefore, we can confidently state that EmailService no longer holds these two responsibilities.\nSome more examples # As we saw earlier, SRP applies to various coding aspects beyond just structs. We observed how it can be violated within a function, although that example was overshadowed by the broken SRP within a struct. To gain a better understanding of how the SRP principle applies to functions, let\u0026rsquo;s examine the example below:\nSRP broken by a function\nimport \u0026#34;github.com/dgrijalva/jwt-go\u0026#34; func extractUsername(header http.Header) string { raw := header.Get(\u0026#34;Authorization\u0026#34;) parser := \u0026amp;jwt.Parser{} token, _, err := parser.ParseUnverified(raw, jwt.MapClaims{}) if err != nil { return \u0026#34;\u0026#34; } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return \u0026#34;\u0026#34; } return claims[\u0026#34;username\u0026#34;].(string) } The function extractUsername doesn\u0026rsquo;t have too many lines. It currently handles extracting a raw JWT token from the HTTP header and returning a value for the username if it\u0026rsquo;s present within the token. Once again, you may notice the use of the word \u0026ldquo;and\u0026rdquo;. This method has multiple responsibilities, and no matter how we rephrase its description, we can\u0026rsquo;t avoid using the word \u0026ldquo;and\u0026rdquo; to describe its actions. Instead of focusing on rephrasing its purpose, we should consider restructuring the method itself. Below, you\u0026rsquo;ll find a proposed new code:\nSRP respected by the function\nfunc extractUsername(header http.Header) string { raw := extractRawToken(header) claims := extractClaims(raw) if claims == nil { return \u0026#34;\u0026#34; } return claims[\u0026#34;username\u0026#34;].(string) } func extractRawToken(header http.Header) string { return header.Get(\u0026#34;Authorization\u0026#34;) } func extractClaims(raw string) jwt.MapClaims { parser := \u0026amp;jwt.Parser{} token, _, err := parser.ParseUnverified(raw, jwt.MapClaims{}) if err != nil { return nil } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil } return claims } Now we have two new functions. The first one, extractRawToken, is responsible for extracting a raw JWT token from the HTTP header. If we ever need to change the key in the header that holds the token, we would only need to modify this one method. The second function, extractClaims, handles the extraction of claims from a raw JWT token. Finally, our old function extractUsername retrieves the specific value from the claims after delegating the tasks of token extraction to the underlying methods. There are many more examples of such refactoring possibilities, and we often encounter them in our daily work. We sometimes use suboptimal approaches because of frameworks that dictate the wrong approach or due to our reluctance to provide a proper implementation.\nSRP broken by Active Record\ntype User struct { db *gorm.DB Username string Firstname string Lastname string Birthday time.Time // // some more fields // } func (u User) IsAdult() bool { return u.Birthday.AddDate(18, 0, 0).Before(time.Now()) } func (u *User) Save() error { return u.db.Exec(\u0026#34;INSERT INTO users ...\u0026#34;, u.Username, u.Firstname, u.Lastname, u.Birthday).Error } The example above illustrates the typical implementation of the Active Record pattern. In this case, we have also included business logic within the User struct, not just data storage in the database. Here, we have combined the purposes of the Active Record and Entity patterns from Domain-Driven Design. To write clean code, we should use separate structs: one for persisting data in the database and another to serve as an Entity. The same mistake is evident in the example below:\nSRP broken by Data Access Object\ntype Wallet struct { gorm.Model Amount int `gorm:\u0026#34;column:amount\u0026#34;` CurrencyID int `gorm:\u0026#34;column:currency_id\u0026#34;` } func (w *Wallet) Withdraw(amount int) error { if amount \u0026gt; w.Amount { return errors.New(\u0026#34;there is no enough money in wallet\u0026#34;) } w.Amount -= amount return nil } Once again, we encounter two responsibilities in the code. However, this time, the second responsibility (mapping to a database table using the Gorm package) is not explicitly expressed as an algorithm but through Go tags. Even in this case, the Wallet struct violates the SRP principle as it serves multiple purposes. If we modify the database schema, we must make changes to this struct. Likewise, if we need to update the business rules for withdrawing money, we would need to modify this class.\nStruct for everything\ntype Transaction struct { gorm.Model Amount int `gorm:\u0026#34;column:amount\u0026#34; json:\u0026#34;amount\u0026#34; validate:\u0026#34;required\u0026#34;` CurrencyID int `gorm:\u0026#34;column:currency_id\u0026#34; json:\u0026#34;currency_id\u0026#34; validate:\u0026#34;required\u0026#34;` Time time.Time `gorm:\u0026#34;column:time\u0026#34; json:\u0026#34;time\u0026#34; validate:\u0026#34;required\u0026#34;` } The code snippet provided above is yet another example of violating the SRP, and in my opinion, it\u0026rsquo;s the most unfortunate one! It\u0026rsquo;s challenging to come up with a smaller struct that takes on even more responsibilities. When we examine the Transaction struct, we realize that it\u0026rsquo;s meant to serve as a mapping to a database table, act as a holder for JSON responses in a REST API, and, due to the validation part, it can also function as a JSON body for API requests. It\u0026rsquo;s essentially trying to do it all in one struct. All of these examples require adjustments sooner or later. As long as we maintain them in our code, they are silent issues that will eventually start causing problems in our logic.\nConclusion # The Single Responsibility Principle is the first of the SOLID principles, representing the letter \u0026ldquo;S\u0026rdquo; in the acronym. It asserts that a single code structure should have only one distinct reason to exist, which we interpret as responsibilities. A structure can either hold a responsibility or delegate it. When a structure encompasses multiple responsibilities, it\u0026rsquo;s a signal that we should consider refactoring that piece of code.\nUseful Resources # Martin Fowler Clean Code ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-solid-single-responsibility/","section":"Articles","summary":"There aren’t too many opportunities for a breakthrough in software development. They usually arise from either rewiring our logic after initial misunderstandings or filling in gaps in our knowledge. I appreciate that feeling of deeper understanding. It can happen during a coding session, while reading a book or an online article, or even while sitting on a bus. An internal voice follows, saying, “Ah, yes, that’s how it works.”\nSuddenly, all past mistakes seem to have a logical reason, and future requirements take shape. I experienced such a breakthrough with the SOLID principles, which were first introduced in a document by Uncle Bob and later expounded upon in his book, “Clean Architecture.” In this article, I intend to embark on a journey through all the SOLID principles, providing examples in Go. The first principle on the list, representing the letter ‘S’ in SOLID, is the Single Responsibility Principle.\nWhen we do not respect Single Responsibility # The Single Responsibility Principle (SRP) asserts that each software module should serve a single, specific purpose that could lead to change.\nThe sentence above comes directly from Uncle Bob himself. Initially, its application was linked to modules and the practice of segregating responsibilities based on the organization’s daily tasks. Nowadays, SRP has a broader scope, influencing various aspects of software development. We can apply its principles to classes, functions, modules, and naturally, in Go, even to structs.\nSome Frankenstein of EmailService\ntype EmailService struct { db *gorm.DB smtpHost string smtpPassword string smtpPort int } func NewEmailService(db *gorm.DB, smtpHost string, smtpPassword string, smtpPort int) *EmailService { return \u0026EmailService{ db: db, smtpHost: smtpHost, smtpPassword: smtpPassword, smtpPort: smtpPort, } } func (s *EmailService) Send(from string, to string, subject string, message string) error { email := EmailGorm{ From: from, To: to, Subject: subject, Message: message, } err := s.db.Create(\u0026email).Error if err != nil { log.Println(err) return err } auth := smtp.PlainAuth(\"\", from, s.smtpPassword, s.smtpHost) server := fmt.Sprintf(\"%s:%d\", s.smtpHost, s.smtpPort) err = smtp.SendMail(server, auth, from, []string{to}, []byte(message)) if err != nil { log.Println(err) return err } return nil } Let’s analyze the code block above. In this code, we have a struct called EmailService, which contains only one method, Send. This service is intended for sending emails. Although it may seem okay at first glance, upon closer inspection, we realize that this code violates the Single Responsibility Principle (SRP) in several ways.\n","title":"Practical SOLID in Golang: Single Responsibility Principle","type":"article"},{"content":"","date":"18 September 2023","externalUrl":null,"permalink":"/tags/ddd/","section":"Tags","summary":"","title":"Ddd","type":"tags"},{"content":"","date":"18 September 2023","externalUrl":null,"permalink":"/series/ddd-in-golang/","section":"Series","summary":"","title":"DDD in Golang","type":"series"},{"content":"There are not many code structures that bring me joy whenever I need to write them. The first time I implemented such code was with a lightweight ORM in Go, back when we didn\u0026rsquo;t have one. However, I used ORM for many years, and at some point, when you rely on ORM, using QueryBuilder becomes inevitable. Here, you may notice terms like \u0026ldquo;predicates\u0026rdquo;, and that\u0026rsquo;s where we can find the Specification pattern. It\u0026rsquo;s hard to find any pattern we use as Specification, yet we do not hear its name. I think the only thing harder is to write an application without using this pattern. The Specification has many applications. We can use it for querying, creation, or validation. We may provide a unique code that can do all this work or provide different implementations for different use cases.\nFor Validation # The first use case for the Specification pattern is validation. Typically, we validate data in forms, but this is at the presentation level. Sometimes, we perform validation during creation, such as for Value Objects. In the context of the domain layer, we can use Specifications to validate the states of Entities and filter them from a collection. So, validation at the domain layer has a broader meaning than for user inputs.\nBase Product Specification\ntype Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { IsValid(product Product) bool } A simple Product Specification\ntype HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) IsValid(product Product) bool { return product.Quantity \u0026gt;= h.pieces } In the example above, there is an interface called ProductSpecification. It defines only one method, IsValid, which expects instances of Product and returns a boolean value as a result if the Product passes validation rules. A simple implementation of this interface is HasAtLeast, which verifies the minimum quantity of the Product.\nFunction as a Product Specification\ntype FunctionSpecification func(product Product) bool func (fs FunctionSpecification) IsValid(product Product) bool { return fs(product) } func IsPlastic(product Product) bool { return product.Material == Plastic } func IsDeliverable(product Product) bool { return product.IsDeliverable } More interesting validators are two functions, IsPlastic and IsDeliverable. We can wrap those functions with a specific type, FunctionSpecification. This type embeds a function with the same signature as the two mentioned. Besides that, it provides methods that respect the ProductSpecification interface. This example is a nice feature of Go, where we can define a function as a type and attach a method to it so that it can implicitly implement some interface. In this case, it exposes the method IsValid, which executes the embedded function.\nCombine Product Specification\ntype AndSpecification struct { specifications []ProductSpecification } func NewAndSpecification(specifications ...ProductSpecification) ProductSpecification { return AndSpecification{ specifications: specifications, } } func (s AndSpecification) IsValid(product Product) bool { for _, specification := range s.specifications { if !specification.IsValid(product) { return false } } return true } type OrSpecification struct { specifications []ProductSpecification } func NewOrSpecification(specifications ...ProductSpecification) ProductSpecification { return OrSpecification{ specifications: specifications, } } func (s OrSpecification) IsValid(product Product) bool { for _, specification := range s.specifications { if specification.IsValid(product) { return true } } return false } type NotSpecification struct { specification ProductSpecification } func NewNotSpecification(specification ProductSpecification) ProductSpecification { return NotSpecification{ specification: specification, } } func (s NotSpecification) IsValid(product Product) bool { return !s.specification.IsValid(product) } In addition, there is also one unique Specification, AndSpecification. Such a struct helps us use an object that implements the ProductSpecification interface but groups validation from all Specifications included.\nIn the code snippet above, we may find two additional Specifications. One is OrSpecification, and it, like AndSpecification, executes all Specifications which it holds. Just, in this case, it uses the \u0026ldquo;or\u0026rdquo; algorithm instead of \u0026ldquo;and\u0026rdquo;. The last one is NotSpecification, which negates the result of the embedded Specification. NotSpecification can also be a functional Specification, but I did not want to complicate it too much.\nTest all together\nfunc main() { spec := NewAndSpecification( NewHasAtLeast(10), FunctionSpecification(IsPlastic), FunctionSpecification(IsDeliverable), ) fmt.Println(spec.IsValid(Product{})) // output: false fmt.Println(spec.IsValid(Product{ Material: Plastic, IsDeliverable: true, Quantity: 50, })) // output: true } For Querying # I have already mentioned in this article the application of the Specification pattern as part of ORM. In many cases, you will not need to implement Specifications for this use case, at least if you use any ORM. Excellent implementations of Specification, in the form of predicates, I found in the Ent library from Facebook. From that moment, I did not have a use case to write Specifications for querying. Still, when you find out that your query for Repository on the domain level can be too complex, you need more possibilities to filter desired Entities. Implementation can look like the example below.\nOne big example for Querying\ntype Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { Query() string Value() []interface{} } type AndSpecification struct { specifications []ProductSpecification } func NewAndSpecification(specifications ...ProductSpecification) ProductSpecification { return AndSpecification{ specifications: specifications, } } func (s AndSpecification) Query() string { var queries []string for _, specification := range s.specifications { queries = append(queries, specification.Query()) } query := strings.Join(queries, \u0026#34; AND \u0026#34;) return fmt.Sprintf(\u0026#34;(%s)\u0026#34;, query) } func (s AndSpecification) Value() []interface{} { var values []interface{} for _, specification := range s.specifications { values = append(values, specification.Value()...) } return values } type OrSpecification struct { specifications []ProductSpecification } func NewOrSpecification(specifications ...ProductSpecification) ProductSpecification { return OrSpecification{ specifications: specifications, } } func (s OrSpecification) Query() string { var queries []string for _, specification := range s.specifications { queries = append(queries, specification.Query()) } query := strings.Join(queries, \u0026#34; OR \u0026#34;) return fmt.Sprintf(\u0026#34;(%s)\u0026#34;, query) } func (s OrSpecification) Value() []interface{} { var values []interface{} for _, specification := range s.specifications { values = append(values, specification.Value()...) } return values } type HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) Query() string { return \u0026#34;quantity \u0026gt;= ?\u0026#34; } func (h HasAtLeast) Value() []interface{} { return []interface{}{h.pieces} } func IsPlastic() string { return \u0026#34;material = \u0026#39;plastic\u0026#39;\u0026#34; } func IsDeliverable() string { return \u0026#34;deliverable = 1\u0026#34; } type FunctionSpecification func() string func (fs FunctionSpecification) Query() string { return fs() } func (fs FunctionSpecification) Value() []interface{} { return nil } func main() { spec := NewOrSpecification( NewAndSpecification( NewHasAtLeast(10), FunctionSpecification(IsPlastic), FunctionSpecification(IsDeliverable), ), NewAndSpecification( NewHasAtLeast(100), FunctionSpecification(IsPlastic), ), ) fmt.Println(spec.Query()) // output: ((quantity \u0026gt;= ? AND material = \u0026#39;plastic\u0026#39; AND deliverable = 1) OR (quantity \u0026gt;= ? AND material = \u0026#39;plastic\u0026#39;)) fmt.Println(spec.Value()) // output: [10 100] } In the new implementation, the ProductSpecification interface provides two methods, Query and Values. We use them to get a query string for a particular Specification and the possible values it holds. Once again, we can see additional Specifications, AndSpecification and OrSpecification. In this case, they join all underlying queries, depending on the operator they present, and merge all values. It is questionable to have such Specifications on the domain layer. As you may see from the output, Specifications provide SQL-like syntax, which delves too much into technical details. In this case, the solution would probably be to define interfaces for different Specifications on the domain layer and have actual implementations on the infrastructure layer. Or to restructure the code so that Specifications hold information about field name, operation, and value. Then, have some mapper on the infrastructure layer that can map such Specifications to an SQL query.\nFor Creation # One simple use case for Specifications is to create a complex object that can vary a lot. In such cases, we can combine it with the Factory pattern or use it inside a Domain Service.\nOne big example for Creation\ntype Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { Create(product Product) Product } type AndSpecification struct { specifications []ProductSpecification } func NewAndSpecification(specifications ...ProductSpecification) ProductSpecification { return AndSpecification{ specifications: specifications, } } func (s AndSpecification) Create(product Product) Product { for _, specification := range s.specifications { product = specification.Create(product) } return product } type HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) Create(product Product) Product { product.Quantity = h.pieces return product } func IsPlastic(product Product) Product { product.Material = Plastic return product } func IsDeliverable(product Product) Product { product.IsDeliverable = true return product } type FunctionSpecification func(product Product) Product func (fs FunctionSpecification) Create(product Product) Product { return fs(product) } func main() { spec := NewAndSpecification( NewHasAtLeast(10), FunctionSpecification(IsPlastic), FunctionSpecification(IsDeliverable), ) fmt.Printf(\u0026#34;%+v\u0026#34;, spec.Create(Product{ ID: uuid.New(), })) // output: {ID:86c5db29-8e04-4caf-82e4-91d6906cff12 Material:plastic IsDeliverable:true Quantity:10} } In the example above, we can find a third implementation of Specification. In this scenario, ProductSpecification supports one method, Create, which expects a Product, adapts it, and returns it back. Once again, there is AndSpecification to apply changes defined by multiple Specifications, but there is no OrSpecification. I could not find an actual use case for the OR algorithm during the creation of an object. Even if it is not present, we can introduce NotSpecification, which could work with specific data types like booleans. Still, in this small example, I could not find a good fit for it.\nConclusion # Specification is a pattern that we use everywhere, in many different cases. Today, it isn\u0026rsquo;t easy to provide validation on the domain layer without the usage of Specifications. Specifications can also be used in querying objects from the underlying storage, and today, they are part of ORM. The third usage is for creating complex instances, where we can combine it with the Factory pattern.\nUseful Resources # Martin Fowler Domain Language ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-specification/","section":"Articles","summary":"There are not many code structures that bring me joy whenever I need to write them. The first time I implemented such code was with a lightweight ORM in Go, back when we didn’t have one. However, I used ORM for many years, and at some point, when you rely on ORM, using QueryBuilder becomes inevitable. Here, you may notice terms like “predicates”, and that’s where we can find the Specification pattern. It’s hard to find any pattern we use as Specification, yet we do not hear its name. I think the only thing harder is to write an application without using this pattern. The Specification has many applications. We can use it for querying, creation, or validation. We may provide a unique code that can do all this work or provide different implementations for different use cases.\nFor Validation # The first use case for the Specification pattern is validation. Typically, we validate data in forms, but this is at the presentation level. Sometimes, we perform validation during creation, such as for Value Objects. In the context of the domain layer, we can use Specifications to validate the states of Entities and filter them from a collection. So, validation at the domain layer has a broader meaning than for user inputs.\nBase Product Specification\ntype Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { IsValid(product Product) bool } A simple Product Specification\ntype HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) IsValid(product Product) bool { return product.Quantity \u003e= h.pieces } In the example above, there is an interface called ProductSpecification. It defines only one method, IsValid, which expects instances of Product and returns a boolean value as a result if the Product passes validation rules. A simple implementation of this interface is HasAtLeast, which verifies the minimum quantity of the Product.\nFunction as a Product Specification\ntype FunctionSpecification func(product Product) bool func (fs FunctionSpecification) IsValid(product Product) bool { return fs(product) } func IsPlastic(product Product) bool { return product.Material == Plastic } func IsDeliverable(product Product) bool { return product.IsDeliverable } More interesting validators are two functions, IsPlastic and IsDeliverable. We can wrap those functions with a specific type, FunctionSpecification. This type embeds a function with the same signature as the two mentioned. Besides that, it provides methods that respect the ProductSpecification interface. This example is a nice feature of Go, where we can define a function as a type and attach a method to it so that it can implicitly implement some interface. In this case, it exposes the method IsValid, which executes the embedded function.\n","title":"Practical DDD in Golang: Specification","type":"article"},{"content":"Today, it is hard to imagine writing an application without accessing some form of storage at runtime. This includes not only writing application code but also deployment scripts, which often need to access configuration files, which are also a type of storage in a sense. When developing applications to solve real-world business problems, connecting to databases, external APIs, caching systems, or other forms of storage is practically unavoidable. It\u0026rsquo;s no surprise, then, that Domain-Driven Design (DDD) includes patterns like the Repository pattern to address these needs. While DDD didn\u0026rsquo;t invent the Repository pattern, it added more clarity and context to its usage.\nThe Anti-Corruption Layer # Domain-Driven Design (DDD) is a principle that can be applied to various aspects of software development and in different parts of a software system. However, its primary focus is on the domain layer, which is where our core business logic resides. While the Repository pattern is responsible for handling technical details related to external data storage and doesn\u0026rsquo;t inherently belong to the business logic, there are situations where we need to access the Repository from within the domain layer.\nSince the domain layer is typically isolated from other layers and doesn\u0026rsquo;t directly communicate with them, we define the Repository within the domain layer, but we define it as an interface. This interface serves as an abstraction that allows us to interact with external data storage without tightly coupling the domain layer to specific technical details or implementations.\nA Simple Repository example\ntype Customer struct { ID uuid.UUID // // some fields // } type Customers []Customer type CustomerRepository interface { GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error) DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) } The interface that defines method signatures within our domain layer is referred to as a \u0026ldquo;Contract.\u0026rdquo; In the example provided, we have a simple Contract interface that specifies CRUD (Create, Read, Update, Delete) methods. By defining the Repository as this interface, we can use it throughout the domain layer. The Repository interface always expects and returns our Entities, such as Customer and Customers (collections with specific methods attached to them, as defined in Go).\nIt\u0026rsquo;s important to note that the Entity Customer doesn\u0026rsquo;t contain any information about the underlying storage type, such as Go tags for defining JSON structures, Gorm columns, or anything of that sort. This kind of low-level storage configuration is typically handled in the infrastructure layer.\nThe Domain Layer\ntype CustomerRepository interface { GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error) DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) } DAO on the Infrastructure Layer\ntype CustomerGorm struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` UUID string `gorm:\u0026#34;uniqueIndex;column:uuid\u0026#34;` // // some fields // } func (c CustomerGorm) ToEntity() (model.Customer, error) { parsed, err := uuid.Parse(c.UUID) if err != nil { return Customer{}, err } return model.Customer{ ID: parsed, // // some fields // }, nil } Repository on the Infrastructure Layer\ntype CustomerRepository struct { connection *gorm.DB } func (r *CustomerRepository) GetCustomer(ctx context.Context, ID uuid.UUID) (*model.Customer, error) { var row CustomerGorm err := r.connection.WithContext(ctx).Where(\u0026#34;uuid = ?\u0026#34;, ID).First(\u0026amp;row).Error if err != nil { return nil, err } customer, err := row.ToEntity() if err != nil { return nil, err } return \u0026amp;customer, nil } // // other methods // In the example above, you can observe a snippet of CustomerRepository implementation. Internally, it utilizes Gorm for smoother integration, but you can also use pure SQL queries if preferred. Lately, I\u0026rsquo;ve been using the Ent library extensively. In this example, you encounter two distinct structures: Customer and CustomerGorm.\nThe first structure serves as an Entity, intended for housing our business logic, domain invariants, and rules. It remains oblivious to the underlying database. The second structure functions as a Data Access Objects (DAO, responsible solely for mapping data to and from the storage system. This structure doesn\u0026rsquo;t have any other role aside from facilitating the mapping of database data to our Entity.\nThe separation of these two structures is a fundamental aspect of using the Repository pattern as an Anti-Corruption layer in our application. It ensures that technical details related to table structure don\u0026rsquo;t contaminate our business logic. What are the implications of this approach? Firstly, it necessitates the management of two types of structures: one for business logic and one for storage. Additionally, a third structure is often introduced, which serves as a Data Transfer Objects (DTO for our API. This approach introduces complexity into our application and entails the creation of multiple mapping functions, as exemplified in the code snippet below. It\u0026rsquo;s essential to thoroughly test such methods to prevent common copy-paste errors.\nEntities on the Domain Layer\ntype Customer struct { ID uuid.UUID Person *Person Company *Company Address Address } type Person struct { SSN string FirstName string LastName string Birthday Birthday } type Birthday time.Time type Company struct { Name string RegistrationNumber string RegistrationDate time.Time } type Address struct { Street string Number string Postcode string City string } DAOs on the Infrastructure Layer\ntype CustomerGorm struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` UUID string `gorm:\u0026#34;uniqueIndex;column:id\u0026#34;` PersonID uint `gorm:\u0026#34;column:person_id\u0026#34;` Person *PersonGorm `gorm:\u0026#34;foreignKey:PersonID\u0026#34;` CompanyID uint `gorm:\u0026#34;column:company_id\u0026#34;` Company *CompanyGorm `gorm:\u0026#34;foreignKey:CompanyID\u0026#34;` Street string `gorm:\u0026#34;column:street\u0026#34;` Number string `gorm:\u0026#34;column:number\u0026#34;` Postcode string `gorm:\u0026#34;column:postcode\u0026#34;` City string `gorm:\u0026#34;column:city\u0026#34;` } func (c CustomerGorm) ToEntity() (model.Customer, error) { parsed, err := uuid.Parse(c.UUID) if err != nil { return model.Customer{}, err } return model.Customer{ ID: parsed, Person: c.Person.ToEntity(), Company: c.Company.ToEntity(), Address: Address{ Street: c.Street, Number: c.Number, Postcode: c.Postcode, City: c.City, }, }, nil } type PersonGorm struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` SSN string `gorm:\u0026#34;uniqueIndex;column:ssn\u0026#34;` FirstName string `gorm:\u0026#34;column:first_name\u0026#34;` LastName string `gorm:\u0026#34;column:last_name\u0026#34;` Birthday time.Time `gorm:\u0026#34;column:birthday\u0026#34;` } func (p *PersonGorm) ToEntity() *model.Person { if p == nil { return nil } return \u0026amp;model.Person{ SSN: p.SSN, FirstName: p.FirstName, LastName: p.LastName, Birthday: Birthday(p.Birthday), } } type CompanyGorm struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` Name string `gorm:\u0026#34;column:name\u0026#34;` RegistrationNumber string `gorm:\u0026#34;column:registration_number\u0026#34;` RegistrationDate time.Time `gorm:\u0026#34;column:registration_date\u0026#34;` } func (c *CompanyGorm) ToEntity() *model.Company { if c == nil { return nil } return \u0026amp;model.Company{ Name: c.Name, RegistrationNumber: c.RegistrationNumber, RegistrationDate: c.RegistrationDate, } } func NewRow(customer model.Customer) CustomerGorm { var person *PersonGorm if customer.Person != nil { person = \u0026amp;PersonGorm{ SSN: customer.Person.SSN, FirstName: customer.Person.FirstName, LastName: customer.Person.LastName, Birthday: time.Time(customer.Person.Birthday), } } var company *CompanyGorm if customer.Company != nil { company = \u0026amp;CompanyGorm{ Name: customer.Company.Name, RegistrationNumber: customer.Company.RegistrationNumber, RegistrationDate: customer.Company.RegistrationDate, } } return CustomerGorm{ UUID: uuid.NewString(), Person: person, Company: company, Street: customer.Address.Street, Number: customer.Address.Number, Postcode: customer.Address.Postcode, City: customer.Address.City, } } However, despite the additional maintenance involved, this approach adds significant value to our codebase. It allows us to represent our Entities within the domain layer in a manner that best encapsulates our business logic. We are not restricted by the storage solution we employ. For instance, we can use one type of identifier within our business logic (such as UUID) and a different one for the database (unsigned integer). This flexibility extends to any data we wish to use for the database and business logic.\nWhen modifications are made in either of these layers, it is likely that we will need to make corresponding adjustments in mapping functions, while the rest of the layer remains untouched (or at least minimally impacted). We can opt to switch to a different database system like MongoDB or Cassandra, or even switch to an external API, all without affecting our domain layer.\nPersistence # The Repository primarily serves for querying purposes and integrates seamlessly with another DDD pattern known as Specification, as you may have observed in the examples. While it can be used without Specification, it often simplifies our workflow. The second key function of the Repository is Persistence. It encompasses the logic for persisting our data in the underlying storage, ensuring its permanence, facilitating updates, and even enabling deletion when necessary.\nGenerate UUID\ntype CustomerRepository struct { connection *gorm.DB } func (r *CustomerRepository) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) { row := NewRow(customer) err := r.connection.WithContext(ctx).Save(\u0026amp;row).Error if err != nil { return nil, err } customer, err = row.ToEntity() if err != nil { return nil, err } return \u0026amp;customer, nil } In some scenarios, we opt for generating unique identifiers within an application. In such cases, the Repository is the appropriate location for this task. In the provided example, you can observe that we generate a new UUID before creating the database record. We can employ a similar approach with integers if we aim to avoid relying on auto-incrementing database keys. Regardless of the method chosen, when we prefer not to depend on database-generated keys, it is advisable to create identifiers within the Repository.\nDatabase Transactions\ntype CustomerRepository struct { connection *gorm.DB } func (r *CustomerRepository) CreateCustomer(ctx context.Context, customer Customer) (*Customer, error) { tx := r.connection.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() if err := tx.Error; err != nil { return nil, err } // // some code // var total int64 var err error if customer.Person != nil { err = tx.Model(PersonGorm{}).Where(\u0026#34;ssn = ?\u0026#34;, customer.Person.SSN).Count(\u0026amp;total).Error } else if customer.Person != nil { err = tx.Model(CompanyGorm{}).Where(\u0026#34;registration_number = ?\u0026#34;, customer.Person.SSN).Count(\u0026amp;total).Error } if err != nil { tx.Rollback() return nil, err } else if total \u0026gt; 0 { tx.Rollback() return nil, errors.New(\u0026#34;there is already such record in DB\u0026#34;) } // // some code // err = tx.Save(\u0026amp;row).Error if err != nil { tx.Rollback() return nil, err } err = tx.Commit().Error if err != nil { tx.Rollback() return nil, err } customer := row.ToEntity() return \u0026amp;customer, nil } Another important function of the Repository is managing transactions. When we need to persist data and perform multiple queries that operate on the same extensive set of tables, it is a suitable situation to establish a transaction, which should be managed within the Repository.\nIn the provided example, we are verifying the uniqueness of a Person or Company. If they already exist, we return an error. All of these operations can be defined as part of a single transaction, and if any part of it fails, we can roll it back. In this context, the Repository serves as an ideal location for such code. It\u0026rsquo;s worth noting that, in the future, we might simplify our inserts to the extent that transactions are no longer required. In that case, we won\u0026rsquo;t need to change the Repository\u0026rsquo;s contract, only the internal code.\nTypes of Repositories # It is a mistake to think that we should use the Repository pattern exclusively for databases. While we frequently use it with databases since they are the primary choice for storage, alternative storage options have gained popularity today. As previously mentioned, we can utilize MongoDB or Cassandra as alternatives. Repositories can also be employed to manage our cache, where Redis, for instance, would be a suitable choice. Repositories can even be applied to REST APIs or configuration files when necessary.\nRedis Repository\ntype CustomerRepository struct { client *redis.Client } func (r *CustomerRepository) GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) { data, err := r.client.Get(ctx, fmt.Sprintf(\u0026#34;user-%s\u0026#34;, ID.String())).Result() if err != nil { return nil, err } var row CustomerJSON err = json.Unmarshal([]byte(data), \u0026amp;row) if err != nil { return nil, err } customer := row.ToEntity() return \u0026amp;customer, nil } REST API Repository\ntype CustomerRepository struct { client *http.Client baseUrl string } func (r *CustomerRepository) GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) { resp, err := r.client.Get(path.Join(r.baseUrl, \u0026#34;users\u0026#34;, ID.String())) if err != nil { return nil, err } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() var row CustomerJSON err = json.Unmarshal(data, \u0026amp;row) if err != nil { return nil, err } customer := row.ToEntity() return \u0026amp;customer, nil } Now we can truly appreciate the advantage of separating our business logic from technical details. By maintaining the same interface for our Repository, our domain layer remains unchanged. However, as our application expands, we may find that MySQL is no longer the ideal solution for our distributed application. In the event of a migration, we can transition without concern for how it will impact our business logic, as long as we maintain consistent interfaces.\nTherefore, your Repository Contract should always revolve around your business logic, while your Repository implementation can use internal structures that can later be mapped to Entities.\nConclusion # The Repository is a well-established pattern responsible for querying and persisting data in the underlying storage. It serves as the primary point for Anti-Corruption within our application. We define it as a Contract within the domain layer and house the actual implementation within the infrastructure layer. It is where we generate application-specific identifiers and manage transactions.\nUseful Resources # Martin Fowler Domain Language ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-repository/","section":"Articles","summary":"Today, it is hard to imagine writing an application without accessing some form of storage at runtime. This includes not only writing application code but also deployment scripts, which often need to access configuration files, which are also a type of storage in a sense. When developing applications to solve real-world business problems, connecting to databases, external APIs, caching systems, or other forms of storage is practically unavoidable. It’s no surprise, then, that Domain-Driven Design (DDD) includes patterns like the Repository pattern to address these needs. While DDD didn’t invent the Repository pattern, it added more clarity and context to its usage.\nThe Anti-Corruption Layer # Domain-Driven Design (DDD) is a principle that can be applied to various aspects of software development and in different parts of a software system. However, its primary focus is on the domain layer, which is where our core business logic resides. While the Repository pattern is responsible for handling technical details related to external data storage and doesn’t inherently belong to the business logic, there are situations where we need to access the Repository from within the domain layer.\nSince the domain layer is typically isolated from other layers and doesn’t directly communicate with them, we define the Repository within the domain layer, but we define it as an interface. This interface serves as an abstraction that allows us to interact with external data storage without tightly coupling the domain layer to specific technical details or implementations.\nA Simple Repository example\ntype Customer struct { ID uuid.UUID // // some fields // } type Customers []Customer type CustomerRepository interface { GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error) DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) } The interface that defines method signatures within our domain layer is referred to as a “Contract.” In the example provided, we have a simple Contract interface that specifies CRUD (Create, Read, Update, Delete) methods. By defining the Repository as this interface, we can use it throughout the domain layer. The Repository interface always expects and returns our Entities, such as Customer and Customers (collections with specific methods attached to them, as defined in Go).\nIt’s important to note that the Entity Customer doesn’t contain any information about the underlying storage type, such as Go tags for defining JSON structures, Gorm columns, or anything of that sort. This kind of low-level storage configuration is typically handled in the infrastructure layer.\n","title":"Practical DDD in Golang: Repository","type":"article"},{"content":"When I wrote the title of this article, I was trying to remember the first design pattern I had learned from \u0026ldquo;The Gang of Four\u0026rdquo;. I think it was one of the following: Factory Method, Singleton, or Decorator. I am sure that other software engineers have a similar story. When they started learning design patterns, either Factory Method or Abstract Factory was one of the first three they encountered. Today, any derivative of the Factory pattern is essential in Domain-Driven Design, and its purpose remains the same, even after many decades.\nComplex Creations # We use the Factory pattern for any complex object creation or to isolate the creation process from other business logic. Having a dedicated place in the code for such scenarios makes it much easier to test separately. In most cases, when I provide a Factory, it is part of the domain layer, allowing me to use it throughout the application. Below, you can see a simple example of a Factory.\nSimple example\ntype Loan struct { ID uuid.UUID // // some fields // } type LoanFactory interface { CreateShortTermLoan(specification LoanSpecification) Loan CreateLongTermLoan(specification LoanSpecification) Loan } The Factory pattern goes hand-in-hand with the Specification pattern. Here, we have a small example with LoanFactory, LoanSpecification, and Loan. LoanFactory represents the Factory pattern in DDD, and more specifically, the Factory Method. It is responsible for creating and returning new instances of Loan that can vary depending on the payment period.\nVariations # As mentioned, we can implement the Factory pattern in many different ways. The most common form, at least for me, is the Factory Method. In this case, we provide some creational methods to our Factory struct.\nLoan Entity\nconst ( LongTerm = iota ShortTerm ) type Loan struct { ID uuid.UUID Type int BankAccountID uuid.UUID Amount Money RequiredLifeInsurance bool } Loan Factory\ntype LoanFactory struct{} func (f *LoanFactory) CreateShortTermLoan(bankAccountID uuid.UUID, amount Money) Loan { return Loan{ Type: ShortTerm, BankAccountID: bankAccountID, Amount: amount, } } func (f *LoanFactory) CreateLongTermLoan(bankAccountID uuid.UUID, amount Money) Loan { return Loan{ Type: LongTerm, BankAccountID: bankAccountID, Amount: amount, RequiredLifeInsurance: true, } } In the code snippet from above, LoanFactory is now a concrete implementation of the Factory Method. It provides two methods for creating instances of the Loan Entity. In this case, we create the same object, but it can have differences depending on whether the loan is long-term or short-term. The distinctions between the two cases can be even more complex, and each additional complexity is a new reason for the existence of this pattern.\nInvestment interface and implementations\ntype Investment interface { Amount() Money } type EtfInvestment struct { ID uuid.UUID EtfID uuid.UUID InvestedAmount Money BankAccountID uuid.UUID } func (e EtfInvestment) Amount() Money { return e.InvestedAmount } type StockInvestment struct { ID uuid.UUID CompanyID uuid.UUID InvestedAmount Money BankAccountID uuid.UUID } func (s StockInvestment) Amount() Money { return s.InvestedAmount } Investment Factories\ntype InvestmentSpecification interface { Amount() Money BankAccountID() uuid.UUID TargetID() uuid.UUID } type InvestmentFactory interface { Create(specification InvestmentSpecification) Investment } type EtfInvestmentFactory struct{} func (f *EtfInvestmentFactory) Create(specification InvestmentSpecification) Investment { return EtfInvestment{ EtfID: specification.TargetID(), InvestedAmount: specification.Amount(), BankAccountID: specification.BankAccountID(), } } type StockInvestmentFactory struct{} func (f *StockInvestmentFactory) Create(specification InvestmentSpecification) Investment { return StockInvestment{ CompanyID: specification.TargetID(), InvestedAmount: specification.Amount(), BankAccountID: specification.BankAccountID(), } } In the example above, there is a code snippet with the Abstract Factory pattern. In this case, we want to create instances of the Investment interface. Since there are multiple implementations of that interface, this seems like a perfect scenario for implementing the Factory pattern. Both EtfInvestmentFactory and StockInvestmentFactory create instances of the Investment interface. In our code, we can keep them in a map of InvestmentFactory interfaces and use them whenever we want to create an Investment from any BankAccount. This is an excellent use case for the Abstract Factory pattern, as we need to create objects from a wide range of possibilities (and there are even more different types of investments).\nReconstruction # We can also use the Factory pattern in other layers, such as the infrastructure and presentation layers. In these layers, I use it to transform Data Transfer Objects (DTO or Data Access Objects (DAO to Entities and vice versa.\nThe Domain Layer\ntype CryptoInvestment struct { ID uuid.UUID CryptoCurrencyID uuid.UUID InvestedAmount Money BankAccountID uuid.UUID } DAO on the Infrastructure Layer\ntype CryptoInvestmentGorm struct { ID int `gorm:\u0026#34;primaryKey;column:id\u0026#34;` UUID string `gorm:\u0026#34;column:uuid\u0026#34;` CryptoCurrencyID int `gorm:\u0026#34;column:crypto_currency_id\u0026#34;` CryptoCurrency CryptoCurrencyGorm `gorm:\u0026#34;foreignKey:CryptoCurrencyID\u0026#34;` InvestedAmount int `gorm:\u0026#34;column:amount\u0026#34;` InvestedCurrencyID int `gorm:\u0026#34;column:currency_id\u0026#34;` Currency CurrencyGorm `gorm:\u0026#34;foreignKey:InvestedCurrencyID\u0026#34;` BankAccountID int `gorm:\u0026#34;column:bank_account_id\u0026#34;` BankAccount BankAccountGorm `gorm:\u0026#34;foreignKey:BankAccountID\u0026#34;` } Factory on the Infrastructure Layer\ntype CryptoInvestmentDBFactory struct{} func (f *CryptoInvestmentDBFactory) ToEntity(dto CryptoInvestmentGorm) (model.CryptoInvestment, error) { id, err := uuid.Parse(dto.UUID) if err != nil { return model.CryptoInvestment{}, err } cryptoId, err := uuid.Parse(dto.CryptoCurrency.UUID) if err != nil { return model.CryptoInvestment{}, err } currencyId, err := uuid.Parse(dto.Currency.UUID) if err != nil { return model.CryptoInvestment{}, err } accountId, err := uuid.Parse(dto.BankAccount.UUID) if err != nil { return model.CryptoInvestment{}, err } return model.CryptoInvestment{ ID: id, CryptoCurrencyID: cryptoId, InvestedAmount: model.NewMoney(dto.InvestedAmount, currencyId), BankAccountID: accountId, }, nil } CryptoInvestmentDBFactory is a Factory located within the infrastructure layer, and it is used to reconstruct the CryptoInvestment Entity. In this example, there is only a method for transforming a DAO to an Entity, but the same Factory can have a method for transforming an Entity into a DAO as well. Since CryptoInvestmentDBFactory uses structures from both the infrastructure (CryptoInvestmentGorm) and the domain (CryptoInvestment), it must reside within the infrastructure layer. This is because we cannot have any dependencies on other layers inside the domain layer.\nI always prefer to use UUIDs within the business logic and expose only UUIDs in the API response. However, databases do not typically support really well strings or binaries as primary keys, so the Factory is the appropriate place to handle this conversion.\nConclusion # The Factory pattern is a concept rooted in older design patterns from The Gang of Four. It can be implemented as an Abstract Factory or a Factory Method. We use it in cases when we want to separate the creation logic from other business logic. Additionally, we can utilize it to transform our Entities to DTOs and vice versa.\nUseful Resources # Martin Fowler Domain Language ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-factory/","section":"Articles","summary":"When I wrote the title of this article, I was trying to remember the first design pattern I had learned from “The Gang of Four”. I think it was one of the following: Factory Method, Singleton, or Decorator. I am sure that other software engineers have a similar story. When they started learning design patterns, either Factory Method or Abstract Factory was one of the first three they encountered. Today, any derivative of the Factory pattern is essential in Domain-Driven Design, and its purpose remains the same, even after many decades.\nComplex Creations # We use the Factory pattern for any complex object creation or to isolate the creation process from other business logic. Having a dedicated place in the code for such scenarios makes it much easier to test separately. In most cases, when I provide a Factory, it is part of the domain layer, allowing me to use it throughout the application. Below, you can see a simple example of a Factory.\nSimple example\ntype Loan struct { ID uuid.UUID // // some fields // } type LoanFactory interface { CreateShortTermLoan(specification LoanSpecification) Loan CreateLongTermLoan(specification LoanSpecification) Loan } The Factory pattern goes hand-in-hand with the Specification pattern. Here, we have a small example with LoanFactory, LoanSpecification, and Loan. LoanFactory represents the Factory pattern in DDD, and more specifically, the Factory Method. It is responsible for creating and returning new instances of Loan that can vary depending on the payment period.\nVariations # As mentioned, we can implement the Factory pattern in many different ways. The most common form, at least for me, is the Factory Method. In this case, we provide some creational methods to our Factory struct.\nLoan Entity\nconst ( LongTerm = iota ShortTerm ) type Loan struct { ID uuid.UUID Type int BankAccountID uuid.UUID Amount Money RequiredLifeInsurance bool } Loan Factory\ntype LoanFactory struct{} func (f *LoanFactory) CreateShortTermLoan(bankAccountID uuid.UUID, amount Money) Loan { return Loan{ Type: ShortTerm, BankAccountID: bankAccountID, Amount: amount, } } func (f *LoanFactory) CreateLongTermLoan(bankAccountID uuid.UUID, amount Money) Loan { return Loan{ Type: LongTerm, BankAccountID: bankAccountID, Amount: amount, RequiredLifeInsurance: true, } } In the code snippet from above, LoanFactory is now a concrete implementation of the Factory Method. It provides two methods for creating instances of the Loan Entity. In this case, we create the same object, but it can have differences depending on whether the loan is long-term or short-term. The distinctions between the two cases can be even more complex, and each additional complexity is a new reason for the existence of this pattern.\n","title":"Practical DDD in Golang: Factory","type":"article"},{"content":"I have spent years understanding and practicing the DDD approach. Most of the principles were easy to understand and implement in the code. However, there was one that particularly caught my attention. I must say that the Aggregate pattern is the most critical one in DDD, and perhaps the entire Tactical Domain-Driven Design doesn\u0026rsquo;t make sense without it. It serves to bind business logic together. While reading, you might think that the Aggregate resembles a cluster of patterns, but that is a misconception. The Aggregate is the central point of the domain layer. Without it, there is no reason to use DDD.\nBusiness Invariants # In the real business world, some rules are flexible. For example, when you take a loan from a bank, you need to pay some interest over time. The overall amount of interest is adjustable and depends on your invested capital and the period you will spend to pay the debt. In some cases, the bank may grant you a grace period, offer you a better overall credit deal due to your loyalty in the past, provide you with a once-in-a-lifetime offer, or require you to place a mortgage on a house.\nAll of these flexible rules from the business world are implemented in DDD using the Policy pattern. They depend on many specific cases and, as a result, require more complex code structures. In the real business world, there are also some immutable rules. Regardless of what we try, we cannot change these rules or their application in our business. These rules are known as Business Invariants. For example, nobody should be allowed to delete a customer account in a bank if any of the bank accounts associated with the customer has money or is in debt. In many banks, one customer may have multiple bank accounts with the same currency. However, in some of them, the customer is not allowed to have any foreign currency accounts or multiple accounts with the same currency. When such business rules exist, they become Business Invariants. They are present from the moment we create the object until the moment we delete it. Breaking them means breaking the whole purpose of the application.\nCurrency Entity\ntype Currency struct { id uuid.UUID // // some fields // } func (c Currency) Equal(other Currency) bool { return c.id == other.id } BankAccount Entity\ntype BankAccount struct { id uuid.UUID iban string amount int currency Currency } func NewBankAccount(currency Currency) BankAccount { return BankAccount{ // // define fields // } } func (ba BankAccount) HasMoney() bool { return ba.amount \u0026gt; 0 } func (ba BankAccount) InDebt() bool { return ba.amount \u0026gt; 0 } func (ba BankAccount) IsForCurrency(currency Currency) bool { return ba.currency.Equal(currency) } BankAccounts Value Object\ntype BankAccounts []BankAccount func (bas BankAccounts) HasMoney() bool { for _, ba := range bas { if ba.HasMoney() { return true } } return false } func (bas BankAccounts) InDebt() bool { for _, ba := range bas { if ba.InDebt() { return true } } return false } func (bas BankAccounts) HasCurrency(currency Currency) bool { for _, ba := range bas { if ba.IsForCurrency(currency) { return true } } return false } CustomerAccount Entity and Aggregate\ntype CustomerAccount struct { id uuid.UUID isDeleted bool accounts BankAccounts // // some fields // } func (ca *CustomerAccount) MarkAsDeleted() error { if ca.accounts.HasMoney() { return errors.New(\u0026#34;there are still money on bank account\u0026#34;) } if ca.accounts.InDebt() { return errors.New(\u0026#34;bank account is in debt\u0026#34;) } ca.isDeleted = true return nil } func (ca *CustomerAccount) CreateAccountForCurrency(currency Currency) error { if ca.accounts.HasCurrency(currency) { return errors.New(\u0026#34;there is already bank account for that currency\u0026#34;) } ca.accounts = append(o.accounts, NewBankAccount(currency)) return nil } In the example above, we can see a Go code construct with CustomerAccount as an Entity and Aggregate. Additionally, there are BankAccount and Currency as Entities. Individually, all three entities have their own business rules. Some rules are flexible, while others are invariants. However, when they interact with each other, certain invariants affect all of them. This is the area where we place our Aggregate.\nWe have a logic for BankAccount creation that depends on all BankAccounts of a particular CustomerAccount. In this case, one Customer cannot have multiple BankAccounts with the same Currency. Furthermore, we cannot delete a CustomerAccount if all BankAccounts connected to it are not in a clean state, meaning they should not have any money in them.\nBusiness Invariants The diagram above displays a group of three entities we\u0026rsquo;ve previously discussed. They are all interconnected by Business Invariants that guarantee the Aggregate is consistently in a dependable state. If any other Entity or Value Object is governed by the same Business Invariants, those new objects also become components of the same Aggregate. However, if within the same Aggregate, we lack a single Invariant that links one object to the rest, then that object does not belong to that Aggregate.\nBoundary # Many times I have used DDD, there was a question about how to define the Aggregate boundary. By adding every new Entity or Value Object into the game, that question always rises. Till now, it is clear that Aggregate is not just some collection of objects. It is a domain concept. Its members define a logical cluster. Without grouping them, we can not guarantee that they are in a valid state.\nPerson Entity\ntype Person struct { id uuid.UUID // // some fields // birthday time.Time } func (p *Person) IsLegal() bool { return p.birthday.AddDate(18, 0, 0).Before(time.Now()) } Company Entity\ntype Company struct { id uuid.UUID // // some fields // isLiquid bool } func (c *Company) IsLegal() bool { return c.isLiquid } Customer Entity and Aggregate\ntype Customer struct { id uuid.UUID person *Person company *Company // // some fields // } func (c *Customer) IsLegal() bool { if c.person != nil { return c.person.IsLegal() } else { return c.company.IsLegal() } } In the code snippet above, you can see the Customer Aggregate. In many applications, you will typically have an Entity called Customer, and often, that Entity will also serve as the Aggregate. Here, we have some Business Invariants that determine the validity of a specific Customer, depending on whether it is a Person or a Company. While there could be more Business Invariants, for now, one suffices. Since we are developing a banking application, the question arises: Do CustomerAccount and Customer belong to the same Aggregate? There is a connection between them, and certain business rules link them, but are these rules considered Invariants?\nAggregate Boundary One Customer can have multiple CustomerAccounts (or none at all). We have observed that there are certain Business Invariants associated with objects related to Customer and other Invariants related to CustomerAccount. To adhere to the precise definition of Invariants, if we cannot identify any that connect Customer and CustomerAccount together, then it is advisable to separate them into distinct Aggregates. This same consideration applies to any other cluster of objects we introduce: Do they share any Invariants with the existing Aggregates?\nAll Aggregates It\u0026rsquo;s always a good practice to keep Aggregates as small as possible. Aggregate members are typically persisted together in storage, such as a database, and adding too many tables within a single transaction can be problematic. In this context, it\u0026rsquo;s evident that we should define a Repository at the level of the Aggregate and persist all its members exclusively through that Repository, as demonstrated in the example below.\nCustomerRepository\ntype CustomerRepository interface { Search(ctx context.Context, specification CustomerSpecification) ([]Customer, error) Create(ctx context.Context, customer Customer) (*Customer, error) UpdatePerson(ctx context.Context, customer Customer) (*Customer, error) UpdateCompany(ctx context.Context, customer Customer) (*Customer, error) // // and many other methods // } We can define Person and Company as Entities (or Value Objects). However, even if they have their own Identity, we should update them through the Customer by using the CustomerRepository. Working directly with Person or Company or persisting them without Customer and other related objects can break Business Invariants. It\u0026rsquo;s important to ensure that transactions apply to all of them together or, if necessary, can be rolled back as a whole. Deletion of an Aggregate must also occur as a cohesive unit. In other words, when we delete the Customer Entity, we should also delete the Person and Company Entities, as they don\u0026rsquo;t have a reason to exist separately.\nAs you can see, the size of an Aggregate should neither be too small nor too large. It should be precisely bounded by Business Invariants. Everything within that boundary must be used together, and anything outside that boundary belongs to other Aggregates.\nRelationships # As you could see previously in the article, there are relationships between Aggregates. These relationships should always be represented in the code, but they should be kept as simple as possible. To avoid complex connections, it\u0026rsquo;s best to avoid referencing Aggregates directly and instead use Identities for relationships. You can see an example of this in the code snippet below.\nA Wrong Approach with Referencing\ntype CustomerAccount struct { id uuid.UUID // // some fields // customer Customer // the wrong way with referencing // // some fields // } The Right Approach with Identity\ntype CustomerAccount struct { id uuid.UUID // // some fields // customerID uuid.UUID // the right way with identity // // some fields // } The other problem may be with the direction of relationships. The best scenario is when we have a unidirectional connection between them and we avoid any bidirectional relationships. Deciding on the direction of these relationships is not always easy, and it depends on the specific use cases within our Bounded Context.\nFor example, if we are developing software for an ATM where a user interacts with a CustomerAccount using a debit card, we might sometimes need to access the Customer by having its identity in the CustomerAccount. In another scenario, our Bounded Context might be an application that manages all CustomerAccounts for one Customer, where users can authorize and manipulate all BankAccounts. In this case, the Customer should contain a list of Identities associated with CustomerAccounts.\nAggregate Root # All the Aggregates discussed in this article share the same names as some of the Entities, such as the Customer Entity and the Customer Aggregate. These unique Entities are known as Aggregate Roots and are the primary objects within the Aggregates. An Aggregate Root serves as a gateway for accessing all other Entities, Value Objects, and Collections contained within the Aggregate. It is considered the main entry point for interacting with the Aggregate.\nIt is essential to follow the rule that members of an Aggregate should not be changed directly but through the Aggregate Root. The Aggregate Root should expose methods that represent its rich behaviors, define ways to access attributes or objects within it, and provide methods for manipulating that data. Even when an Aggregate Root returns an object, it should return only a copy of it to maintain encapsulation and control over the Aggregate\u0026rsquo;s internal state.\nRich Behaviors\nfunc (ca *CustomerAccount) GetIBANForCurrency(currency Currency) (string, error) { for _, account := range ca.accounts { if account.IsForCurrency(currency) { return account.iban, nil } } return \u0026#34;\u0026#34;, errors.New(\u0026#34;this account does not support this currency\u0026#34;) } func (ca *CustomerAccount) MarkAsDeleted() error { if ca.accounts.HasMoney() { return errors.New(\u0026#34;there are still money on bank account\u0026#34;) } if ca.accounts.InDebt() { return errors.New(\u0026#34;bank account is in debt\u0026#34;) } ca.isDeleted = true return nil } func (ca *CustomerAccount) CreateAccountForCurrency(currency Currency) error { if ca.accounts.HasCurrency(currency) { return errors.New(\u0026#34;there is already bank account for that currency\u0026#34;) } ca.accounts = append(ca.accounts, NewBankAccount(currency)) return nil } func (ca *CustomerAccount) AddMoney(amount int, currency Currency) error { if ca.isDeleted { return errors.New(\u0026#34;account is deleted\u0026#34;) } if ca.isLocked { return errors.New(\u0026#34;account is locked\u0026#34;) } return ca.accounts.AddMoney(amount, currency) } Within an Aggregate, there are typically multiple Entities and Value Objects, each with its own Identity. These Identities can be classified into two types: Global Identity and Local Identity.\nGlobal Identity: The Aggregate Root within the Aggregate has a Global Identity. This Identity is unique globally, meaning that there is no other Entity in the entire application with the same Identity. It is permissible to reference the Global Identity of the Aggregate Root from outside the Aggregate, allowing external parts of the application to interact with the Aggregate.\nLocal Identity: All other Entities and Value Objects within the Aggregate have local Identities. These Identities are unique only within the context of the Aggregate itself. They may be reused for Entities and Value Objects outside the Aggregate. Local Identities are known and managed solely by the Aggregate, and they should not be referenced or exposed outside the boundaries of the Aggregate.\nBy distinguishing between Global and Local Identities, we can maintain consistency and avoid conflicts within the Aggregate while ensuring that the Aggregate Root remains uniquely identifiable throughout the application.\nGlobal and Local Identity\ntype Person struct { id uuid.UUID // local identity // // some fields // } type Company struct { id uuid.UUID // local identity // // some fields // } type Customer struct { id uuid.UUID // global identity person *Person company *Company // // some fields // } Conclusion # An Aggregate is a concept in the domain that follows certain rules called Business Invariants. These rules must always be true, no matter the state of the application. They define the limits or boundaries of an Aggregate. When it comes to storing or removing data, all parts of an Aggregate must be handled together. Aggregate Roots act as entry points to the other elements within the Aggregate. To access these elements, you must go through the Aggregate Roots; you can\u0026rsquo;t reach them directly.\nUseful Resources # Martin Fowler Domain Language ","date":"18 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-aggregate/","section":"Articles","summary":"I have spent years understanding and practicing the DDD approach. Most of the principles were easy to understand and implement in the code. However, there was one that particularly caught my attention. I must say that the Aggregate pattern is the most critical one in DDD, and perhaps the entire Tactical Domain-Driven Design doesn’t make sense without it. It serves to bind business logic together. While reading, you might think that the Aggregate resembles a cluster of patterns, but that is a misconception. The Aggregate is the central point of the domain layer. Without it, there is no reason to use DDD.\nBusiness Invariants # In the real business world, some rules are flexible. For example, when you take a loan from a bank, you need to pay some interest over time. The overall amount of interest is adjustable and depends on your invested capital and the period you will spend to pay the debt. In some cases, the bank may grant you a grace period, offer you a better overall credit deal due to your loyalty in the past, provide you with a once-in-a-lifetime offer, or require you to place a mortgage on a house.\nAll of these flexible rules from the business world are implemented in DDD using the Policy pattern. They depend on many specific cases and, as a result, require more complex code structures. In the real business world, there are also some immutable rules. Regardless of what we try, we cannot change these rules or their application in our business. These rules are known as Business Invariants. For example, nobody should be allowed to delete a customer account in a bank if any of the bank accounts associated with the customer has money or is in debt. In many banks, one customer may have multiple bank accounts with the same currency. However, in some of them, the customer is not allowed to have any foreign currency accounts or multiple accounts with the same currency. When such business rules exist, they become Business Invariants. They are present from the moment we create the object until the moment we delete it. Breaking them means breaking the whole purpose of the application.\nCurrency Entity\ntype Currency struct { id uuid.UUID // // some fields // } func (c Currency) Equal(other Currency) bool { return c.id == other.id } BankAccount Entity\n","title":"Practical DDD in Golang: Aggregate","type":"article"},{"content":"At first glance, Modules may not seem like a typical software development pattern, especially when we often associate patterns with specific code structures or behaviors. This can be particularly confusing when considering Go Modules. These modules consist of closely related Go Packages, are versioned, and released together, serving as a form of dependency management in Go. Since both Go Modules and Packages impact the project\u0026rsquo;s structure, it raises the question of their relationship with the DDD pattern known as Module. Indeed, there is a connection between them.\nThe Structure # In Go, we use Packages to organize and group our code. Packages are closely tied to the folder structure within our projects, although there can be variations in naming. These variations arise because we have the flexibility to name our package differently than the actual folder it resides in.\nFolder pkg/access/domain/model\npackage access_model import ( \u0026#34;github.com/google/uuid\u0026#34; ) type User struct { ID uuid.UUID // // some fields // } Folder pkg/access/domain/service\npackage access_service import ( \u0026#34;project/pkg/access/domain/model\u0026#34; ) type UserService interface { Create(user access_model.User) error // // some methods // } In the example above, you can observe slight differences between folder and package naming. In some cases, when dealing with multiple model packages, I add prefixes from my DDD Modules to facilitate referencing these packages within the same file. Now, we can start to gain a better understanding of what a DDD Module would be in the previous example. In this context, the Module encompasses the access package along with all its child packages.\nproject ├── cmd │ ├── main.go ├── internal │ ├── module1 │ │ ├── infrastructure │ │ ├── presentation │ │ ├── application │ │ ├── domain │ │ │ ├── service │ │ │ ├── factory │ │ │ ├── repository │ │ │ └── model │ │ └── module1.go │ ├── module2 │ │ └── ... │ └── ... ├── pkg │ ├── module3 │ │ └── ... │ ├── module4 │ │ └── ... │ └── ... ├── go.mod └── ... The folder structure in the diagram above represents my preferred project structure for implementing Domain-Driven Design in Go. While I may create different variations of certain folders, I strive to maintain consistent DDD Modules.\nIn my projects, each Module typically consists of no more than four base packages: infrastructure, presentation, application, and domain. As you can see, I adhere to the principles of Hexagonal Architecture. In this structure, I place the infrastructure package at the top. This is because, by following Uncle Bob\u0026rsquo;s Dependency Inversion Principle, my low-level Services from the infrastructure layer implement high-level interfaces from other layers. With this approach, I ensure that I define a Port, such as the UserRepository interface, in the domain layer, while the actual implementation resides in the infrastructure layer. There can be multiple Adapters for this implementation, like UserDBRepository or UserFakeRepository.\nFolder pkg/access/domain/repository\npackage acces_repository import ( \u0026#34;project/pkg/access/domain/model\u0026#34; ) type UserRepository interface { Create(user access_model.User) error } Folder pkg/access/infrastructure/database\npackage database type UserDBRepository struct { // // some fields // } func (r *UserDBRepository) Create(user access_model.User) error { // // some code // return nil } Folder pkg/access/infrastructure/fake\ntype UserFakeRepository struct { // // some fields // } func (r *UserFakeRepository) Create(user access_model.User) error { // // some code // return nil } The concept of Ports and Adapters is not new, and it is part of the principles of Hexagonal Architecture. It is one of the principles I use when designing my DDD Modules, and in my opinion, it is a crucial one. Returning to the package structure within the Module, in this design, each layer has knowledge of all the layers below it, but no knowledge of the layers above it. This means that the infrastructure layer can depend on all other layers, while the domain layer depends on none. Just below the infrastructure layer is the presentation layer, which we can also refer to as the interface layer (although \u0026ldquo;interface\u0026rdquo; is a reserved word in Go, so \u0026ldquo;presentation\u0026rdquo; is a suitable alternative). Finally, situated between the presentation and domain layers is the application layer.\nThe advantage of this layering approach in Go is that it helps us avoid cyclic dependencies, which can lead to compile-time issues in our code. By adhering to these layering rules and dependency directions, we can save ourselves from the headaches of complex code refactoring. You may have also noticed some folders (or packages) within the domain layer, such as model and service. I include them on occasion to keep my packages as straightforward as possible.\nThe Logical Cluster # A DDD Module isn\u0026rsquo;t just a random collection of files and folders grouped together. The code contained within those files and folders should form a cohesive and logical structure. Furthermore, two different Modules should be designed to be loosely coupled, with minimal dependencies between them.\nThis approach helps in keeping the codebase organized, maintainable, and modular. It also facilitates the separation of concerns and allows for better isolation of different parts of the system. Each Module should have a clear purpose and responsibility, making it easier to understand and maintain the codebase as it grows.\nproject ├── ... ├── pkg │ ├── access │ │ ├── infrastructure │ │ │ └── ... │ │ ├── presentation │ │ │ └── ... │ │ ├── application │ │ │ └── service │ │ │ ├── authorization.go │ │ │ └── registration.go │ │ ├── domain │ │ │ ├── repository │ │ │ │ ├── user.go │ │ │ │ ├── group.go │ │ │ │ └── role.go │ │ │ └── model │ │ │ ├── user.go │ │ │ ├── group.go │ │ │ └── role.go │ │ └── access.go │ ├── shopping │ │ ├── infrastructure │ │ │ └── ... │ │ ├── presentation │ │ │ └── ... │ │ ├── application │ │ │ └── service │ │ │ └── session_basket.go │ │ ├── domain │ │ │ ├── service │ │ │ │ └── shopping.go │ │ │ ├── factory │ │ │ │ └── basket.go │ │ │ ├── repository │ │ │ │ └── order.go │ │ │ └── model │ │ │ ├── order.go │ │ │ └── basket.go │ │ └── shopping.go │ ├── customer │ │ ├── infrastructure │ │ │ └── ... │ │ ├── presentation │ │ │ └── ... │ │ ├── application │ │ │ └── ... │ │ ├── domain │ │ │ ├── repository │ │ │ │ ├── customer.go │ │ │ │ └── address.go │ │ │ └── model │ │ │ ├── customer.go │ │ │ └── address.go │ │ └── customer.go │ └── ... └── ... The provided folder structure is a straightforward example of DDD Modules. In this structure, there are three Modules: access, shopping, and customer, and potentially more. Each Module is organized into layers and sublayers, and they each serve specific purposes within the application.\nThe access Module deals with authorization, registration, and user session management. It handles user access rights and determines whether users can access particular objects or perform specific actions.\nThe customer Module is responsible for managing customer-related information, including customer profiles and addresses. Customers are entities that can place Orders, and a single user may have multiple Customer profiles for deliveries.\nThe shopping Module is more complex and handles the entire shopping process. It involves creating and managing ShoppingBaskets, Order creation, and interaction with both the access and customer Modules. It depends on both of these Modules to function correctly.\nIt\u0026rsquo;s crucial to manage dependencies between Modules to ensure that they are one-directional. This helps prevent circular dependencies, which can lead to compilation errors and make the codebase harder to maintain. Properly structured Modules enhance code organization and maintainability, making it easier to understand and extend the application.\nModule Dependencies The diagram provided illustrates the dependencies between different Modules in the application. Here\u0026rsquo;s a summary of the dependencies and how the Modules interact:\nThe shopping Module: This Module depends on both the customer and access Modules. It relies on the customer Module to determine the owner of an Order and to access delivery address information. Additionally, it depends on the access Module to check access rights for specific shopping-related actions, such as managing Baskets and Items.\nThe customer Module: The customer Module has a dependency on the access Module. It uses the access Module to access user session information and to determine which Customers are associated with a User. This information is used to decide where to send an Order.\nThe access Module: The access Module is a foundational Module that other Modules depend on. It provides user session management, authorization, and access control. Both the shopping and customer Modules depend on the access Module to handle user-related functionality.\nIt\u0026rsquo;s important to note that a single Module does not necessarily correspond to one Bounded Context. In this example, the access Module could potentially be considered as a candidate for a separate Bounded Context, and future architectural decisions might involve moving it elsewhere. The decision to split the shopping and customer Modules was based on their distinct functionalities and the ability to work with them independently without affecting each other.\nThis modular approach allows for flexibility and maintainability in the application\u0026rsquo;s architecture, making it easier to manage and extend over time.\nThe Naming # Discussing naming might seem surprising, but it\u0026rsquo;s actually quite important. In my experience, I\u0026rsquo;ve encountered poorly chosen names for DDD Modules, and I\u0026rsquo;ve even made some bad naming choices myself.\nproject ├── ... ├── pkg │ ├── shoppingAndCustomer │ │ └── ... │ ├── utils │ │ └── ... │ ├── events │ │ └── ... │ ├── strategy │ │ └── ... │ └── ... └── ... The example above includes several poor names. I always avoid using the word \u0026ldquo;and\u0026rdquo; in Module names, as seen here in shoppingAndCustomer. If I can\u0026rsquo;t avoid using the word \u0026ldquo;and,\u0026rdquo; it probably indicates two separate Modules. The term utils is one of the worst names in software development, and I avoid using it for struct names, file names, function names, packages, or Module names. Naming a Module garbageCollector might describe the contents of the utils Module accurately.\nCreating a Module that contains bits and pieces from all over the codebase is also unhelpful. The events Module is an example of this, as it holds Domain Events from the entire application. Naming a Module after a design pattern, like the strategy Module, is not ideal either. We may use the Strategy pattern in various parts of our application, so it doesn\u0026rsquo;t make sense to have multiple strategy Modules. Instead, our Modules should have names from the real business world, be a part of the Ubiquitous Language, and describe a unique cluster of business logic with a term that belongs to both the business and software development worlds.\nDependency Injection # You may have noticed that the first project structure introduced separate Go files in the roots of each DDD Module. I always name these files either module.go or the same as the Module itself.\nThese files are where I define dependencies within my Module and different Adapters for my Ports when I have them. In many cases, I create simple Go containers that store objects that I use in the application.\nModule File\ntype AccessModule struct { repository acces_repository.UserRepository service access_service.UserService } func NewAccessModule(useDatabase bool) *AccessModule{ var repository acces_repository.UserRepository if useDatabase { repository = \u0026amp;database.UserDBRepository{} } else { repository = \u0026amp;fake.UserFakeRepository{} } var service access_service.UserService // // some code // return \u0026amp;AccessModule{ repository: repository, service: service, } } func (m *AccessModule) GetRepository() acces_repository.UserRepository { return m.repository } func (m *AccessModule) GetService() access_service.UserService { return m.service } In the example above, I\u0026rsquo;ve created the AccessModule struct. During initialization, it accepts configuration that defines whether it should rely on the database or some fake implementation for UserRepository. Later, all other Modules can use this container to obtain their dependencies.\nWe can also address Dependency Injection in Go by utilizing one of the many available frameworks. One of the most commonly used libraries is Wire, but my personal favorite is Dingo. The Dingo library utilizes reflection, which can be a challenging topic for many Go developers. However, despite my reservations about reflection in Go, Dingo has proven to be an easy and stable solution in my experience, offering a range of useful features.\nUsing Dingo library\npackage example type BillingModule struct {} func (module *BillingModule) Configure(injector *dingo.Injector) { // This tells Dingo that whenever it sees a dependency on a TransactionLog, // it should satisfy the dependency using a DatabaseTransactionLog. injector.Bind(new(TransactionLog)).To(DatabaseTransactionLog{}) // Similarly, this binding tells Dingo that when CreditCardProcessor is used in // a dependency, that should be satisfied with a PaypalCreditCardProcessor. injector.Bind(new(CreditCardProcessor)).To(PaypalCreditCardProcessor{}) } Conclusion # DDD Module is a logical cluster for our code, bringing together various structures into a cohesive group that shares specific business rules. Within Modules, we can introduce different layers. It\u0026rsquo;s important to ensure that both layers and Modules maintain one-directional communication to prevent cyclic dependencies. Additionally, Modules should be named using terminology from the business world to promote clarity and understanding.\nUseful Resources # Martin Fowler Domain Language ","date":"17 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-module/","section":"Articles","summary":"At first glance, Modules may not seem like a typical software development pattern, especially when we often associate patterns with specific code structures or behaviors. This can be particularly confusing when considering Go Modules. These modules consist of closely related Go Packages, are versioned, and released together, serving as a form of dependency management in Go. Since both Go Modules and Packages impact the project’s structure, it raises the question of their relationship with the DDD pattern known as Module. Indeed, there is a connection between them.\nThe Structure # In Go, we use Packages to organize and group our code. Packages are closely tied to the folder structure within our projects, although there can be variations in naming. These variations arise because we have the flexibility to name our package differently than the actual folder it resides in.\nFolder pkg/access/domain/model\npackage access_model import ( \"github.com/google/uuid\" ) type User struct { ID uuid.UUID // // some fields // } Folder pkg/access/domain/service\npackage access_service import ( \"project/pkg/access/domain/model\" ) type UserService interface { Create(user access_model.User) error // // some methods // } In the example above, you can observe slight differences between folder and package naming. In some cases, when dealing with multiple model packages, I add prefixes from my DDD Modules to facilitate referencing these packages within the same file. Now, we can start to gain a better understanding of what a DDD Module would be in the previous example. In this context, the Module encompasses the access package along with all its child packages.\nproject ├── cmd │ ├── main.go ├── internal │ ├── module1 │ │ ├── infrastructure │ │ ├── presentation │ │ ├── application │ │ ├── domain │ │ │ ├── service │ │ │ ├── factory │ │ │ ├── repository │ │ │ └── model │ │ └── module1.go │ ├── module2 │ │ └── ... │ └── ... ├── pkg │ ├── module3 │ │ └── ... │ ├── module4 │ │ └── ... │ └── ... ├── go.mod └── ... The folder structure in the diagram above represents my preferred project structure for implementing Domain-Driven Design in Go. While I may create different variations of certain folders, I strive to maintain consistent DDD Modules.\nIn my projects, each Module typically consists of no more than four base packages: infrastructure, presentation, application, and domain. As you can see, I adhere to the principles of Hexagonal Architecture. In this structure, I place the infrastructure package at the top. This is because, by following Uncle Bob’s Dependency Inversion Principle, my low-level Services from the infrastructure layer implement high-level interfaces from other layers. With this approach, I ensure that I define a Port, such as the UserRepository interface, in the domain layer, while the actual implementation resides in the infrastructure layer. There can be multiple Adapters for this implementation, like UserDBRepository or UserFakeRepository.\n","title":"Practical DDD in Golang: Module","type":"article"},{"content":"In many cases, Entities are the most effective means of representing elements in Domain-Driven Design. Together with Value Objects, they can provide a precise reflection of our Problem Domain. However, sometimes, the most apt way to depict a Problem Domain is by employing events that transpire within it. In my experience, I increasingly attempt to identify events and then discern the Entities associated with them. Although Eric Evans didn\u0026rsquo;t cover the Domain Event pattern in the first edition of his book, today, it\u0026rsquo;s challenging to fully develop the domain layer without incorporating events.\nThe Domain Event pattern serves as a representation of such occurrences within our code. We employ it to elucidate any real-world event that holds relevance for our business logic. In the contemporary business landscape, virtually everything is connected to some form of event.\nIt can be anything # Domain Events can encompass a wide range of occurrences, but they must adhere to certain rules. The first rule is that they are immutable. To support this characteristic, I consistently utilize private fields within Event structs, even though I\u0026rsquo;m not particularly fond of private fields and getters in Go. However, Events typically don\u0026rsquo;t require many getters. Additionally, a specific Event can only occur once. This implies that we can create an Order Entity with a particular Identity only once, and consequently, our code can only trigger the Event that describes the creation of that Order once. Any other Event related to that Order would be a different type of Event, pertaining to a distinct Order. Each Event essentially narrates something that has already taken place, representing the past. This means we trigger the OrderCreated Event after we have already created the Order, not before.\nGlobal Events\ntype Event interface { Name() string } type GeneralError string func (e GeneralError) Name() string { return \u0026#34;event.general.error\u0026#34; } Order Event\ntype OrderEvent interface { Event OrderID() uuid.UUID } type OrderDispatched struct { orderID uuid.UUID } func (e OrderDispatched) Name() string { return \u0026#34;event.order.dispatched\u0026#34; } func (e OrderDispatched) OrderID() uuid.UUID { return e.orderID } type OrderDelivered struct { orderID uuid.UUID } func (e OrderDelivered) Name() string { return \u0026#34;event.order.delivery.success\u0026#34; } func (e OrderDelivered) OrderID() uuid.UUID { return e.orderID } type OrderDeliveryFailed struct { orderID uuid.UUID } func (e OrderDeliveryFailed) Name() string { return \u0026#34;event.order.delivery.failed\u0026#34; } func (e OrderDeliveryFailed) OrderID() uuid.UUID { return e.orderID } The code example provided above demonstrates simple Domain Events. This code represents just one of countless ways to implement them in Go. In certain situations, such as with GeneralError, I have employed straightforward strings as Event representations. However, there are instances when I\u0026rsquo;ve utilized more complex objects or extended the base Event interface with a more specific one to introduce additional methods, as seen with OrderEvent.\nIt\u0026rsquo;s worth noting that the Domain Event, as an interface, doesn\u0026rsquo;t require the implementation of any specific methods. It can take on any form you find suitable. As mentioned earlier, I sometimes use strings, but essentially, anything can serve as an adequate representation. Occasionally, for the sake of generalization, I still declare the Event interface.\nThe Old Friend # The Domain Event pattern, fundamentally, is another manifestation of the Observer pattern. The Observer pattern identifies key roles, including Publisher, Subscriber (or Observer), and, naturally, Event. The Domain Event pattern operates on the same principles. The Subscriber, often referred to as the EventHandler, is a structure that should react to a specific Domain Event to which it has subscribed. The Publisher, in this context, is a structure responsible for notifying all EventHandlers when a particular Event occurs. The Publisher serves as the entry point for triggering any Event and contains all the EventHandlers. It offers a straightforward interface for any Domain Service, Factory, or other objects that wish to publish a particular Event.\nObserver pattern in practice\ntype EventHandler interface { Notify(event Event) } type EventPublisher struct { handlers map[string][]EventHandler } func (e *EventPublisher) Subscribe(handler EventHandler, events ...Event) { for _, event := range events { handlers := e.handlers[event.Name()] handlers = append(handlers, handler) e.handlers[event.Name()] = handlers } } func (e *EventPublisher) Notify(event Event) { for _, handler := range e.handlers[event.Name()] { handler.Notify(event) } } The code snippet presented above encompasses the remainder of the Domain Event pattern. The EventHandler interface defines any structure that should respond to a particular Event. It contains only one Notify method, which expects the Event as an argument.\nThe EventPublisher struct is more intricate. It offers the general Notify method, which is responsible for informing all EventHandlers subscribed to a specific Event. Another function, Subscribe, enables any EventHandler to subscribe to any Event. The EventPublisher struct could be less complex; instead of allowing EventHandler to subscribe to a particular Event using a map, it could manage a simple array of EventHandlers and notify all of them for any Event.\nIn general, we should publish Domain Events synchronously in our domain layer. However, there are occasions when I want to trigger them asynchronously, for which I employ Goroutines.\nObserver pattern with goroutines\ntype Event interface { Name() string IsAsynchronous() bool } type EventPublisher struct { handlers map[string][]EventHandler } func (e *EventPublisher) Notify(event Event) { if event.IsAsynchronous() { go e.notify(event) // runs code in separate Go routine } e.notify(event) // synchronous call } func (e *EventPublisher) notify(event Event) { for _, handler := range e.handlers[event.Name()] { handler.Notify(event) } } The example above illustrates one variation for asynchronously publishing Events. To accommodate both approaches, I frequently define a method within the Event interface that later informs me whether the Event should be fired synchronously or not.\nCreation # My biggest dilemma was determining the right place to create an Event. To be honest, I created them everywhere. The only rule I followed was that stateful objects could not notify the EventPublisher. Entities, Value Objects, and Aggregates are stateful objects. From that perspective, they should not contain the EventPublisher inside them, and providing it as an argument to their methods always seemed like messy code to me. I also do not use stateful objects as EventHandlers. If I need to perform an action with some Entity when a specific Event occurs, I create an EventHandler that contains a Repository. Then, the Repository can provide an Entity that needs to be adapted. Still, creating Event objects inside a method of an Aggregate is acceptable. Sometimes, I create them within an Entity\u0026rsquo;s method and return them as a result. Then, I use stateless structures like Domain Service or Factory to notify the EventPublisher.\nOrder Aggregate\ntype Order struct { id uuid.UUID // // some fields // isDispatched bool deliveryAddress Address } func (o Order) ID() uuid.UUID { return o.id } func (o *Order) ChangeAddress(address Address) Event { if o.isDispatched { return DeliveryAddressChangeFailed{ orderID: o.ID(), } } // // some code // return DeliveryAddressChanged{ orderID: o.ID(), } } Order Service\ntype OrderService struct { repository OrderRepository publisher EventPublisher } func (s *OrderService) Create(order Order) (*Order, error) { result, err := s.repository.Create(order) if err != nil { return nil, err } // // update Adrress in DB // s.publisher.Notify(OrderCreated{ orderID: result.ID(), }) return result, err } func (s *OrderService) ChangeAddress(order Order, address Address) { evt := order.ChangeAddress(address) s.publisher.Notify(evt) // publishing of events only inside stateless objects } In the example above, the Order Aggregate provides a method for updating delivery addresses. The result of that method may be an Event. This means that Order can create some Events, but that\u0026rsquo;s its limit. On the other hand, OrderService can both create Events and publish them. It can also fire Events that it receives from Order while updating the delivery address. This is possible because it contains EventPublisher.\nEvents on other layers # We can listen to Domain Events in other layers, like the application, presentation, or infrastructure layers. We can also define separate Events that are dedicated only to those layers. In those cases, we are not dealing with Domain Events. A simple example is Events in the Application Layer. After creating an Order, in most cases, we should send an Email to the customer. Although it may seem like a business rule, sending emails is always application-specific. In the example below, there is a simple code with EmailEvent. As you may guess, an Email can be in many different states, and transitioning from one state to another is always performed during some Events.\nThe Domain Layer\ntype Email struct { id uuid.UUID // // some fields // } type EmailEvent interface { Event EmailID() uuid.UUID } type EmailSent struct { emailID uuid.UUID } func (e EmailSent) Name() string { return \u0026#34;event.email.sent\u0026#34; } func (e EmailSent) EmailID() uuid.UUID { return e.emailID } The Application Layer\ntype EmailHandler struct{ // // some fields // } func (e *EmailHandler) Notify(event Event) { switch actualEvent := event.(type) { case EmailSent: // // do something // default: return } } Sometimes we want to trigger a Domain Event outside of our Bounded Context. These Domain Events are internal to our Bounded Context but are external to other contexts. Although this topic is more related to Strategic Domain-Driven Design, I will briefly mention it here. To publish an Event outside of our Microservice, we may use a messaging service like SQS.\nSend Events to SQS\nimport ( // // some imports // \u0026#34;github.com/aws/aws-sdk-go/aws\u0026#34; \u0026#34;github.com/aws/aws-sdk-go/service/sqs\u0026#34; ) type EventSQSHandler struct { svc *sqs.SQS } func (e *EventSQSHandler) Notify(event Event) { data := map[string]string{ \u0026#34;event\u0026#34;: event.Name(), } body, err := json.Marshal(data) if err != nil { log.Fatal(err) } _, err = e.svc.SendMessage(\u0026amp;sqs.SendMessageInput{ MessageBody: aws.String(string(body)), QueueUrl: \u0026amp;e.svc.Endpoint, }) if err != nil { log.Fatal(err) } } In the code snippet above, you can see the EventSQSHandler, a simple struct in the infrastructure layer. It sends a message to the SQS queue whenever a specific Event occurs, publishing only the Event names without specific details. When it comes to publishing internal Events to the outside world, we may also listen to external Events and map them to internal ones. To achieve this, I always provide a Service on the infrastructure layer that listens to messages from the outside.\nCatch Events from SQS\ntype SQSService struct { svc *sqs.SQS publisher *EventPublisher stopChannel chan bool } func (s *SQSService) Run(event Event) { eventChan := make(chan Event) MessageLoop: for { s.listen(eventChan) select { case event := \u0026lt;-eventChan: s.publisher.Notify(event) case \u0026lt;-s.stopChannel: break MessageLoop } } close(eventChan) close(s.stopChannel) } func (s *SQSService) Stop() { s.stopChannel \u0026lt;- true } func (s *SQSService) listen(eventChan chan Event) { go func() { message, err := s.svc.ReceiveMessage(\u0026amp;sqs.ReceiveMessageInput{ // // some code // }) var event Event if err != nil { log.Print(err) event = NewGeneralError(err) return } else { // // extract message // } eventChan \u0026lt;- event }() } The example above illustrates the SQSService within the infrastructure layer. This service listens to SQS messages and maps them to internal Events when possible. While I haven\u0026rsquo;t used this approach extensively, it has proven valuable in scenarios where multiple Microservices need to respond to events like Order creation or Customer registration.\nConclusion # Domain Events are essential constructs in our domain logic. In today\u0026rsquo;s business world, everything is closely tied to specific events, making it a good practice to describe our Domain Model using events. The Domain Event pattern is essentially an implementation of the Observer pattern. While it can be created within various objects, it is best fired from stateless ones. Additionally, other layers can also make use of Domain Events or implement their own event mechanisms.\nUseful Resources # Martin Fowler Domain Language ","date":"17 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-event/","section":"Articles","summary":"In many cases, Entities are the most effective means of representing elements in Domain-Driven Design. Together with Value Objects, they can provide a precise reflection of our Problem Domain. However, sometimes, the most apt way to depict a Problem Domain is by employing events that transpire within it. In my experience, I increasingly attempt to identify events and then discern the Entities associated with them. Although Eric Evans didn’t cover the Domain Event pattern in the first edition of his book, today, it’s challenging to fully develop the domain layer without incorporating events.\nThe Domain Event pattern serves as a representation of such occurrences within our code. We employ it to elucidate any real-world event that holds relevance for our business logic. In the contemporary business landscape, virtually everything is connected to some form of event.\nIt can be anything # Domain Events can encompass a wide range of occurrences, but they must adhere to certain rules. The first rule is that they are immutable. To support this characteristic, I consistently utilize private fields within Event structs, even though I’m not particularly fond of private fields and getters in Go. However, Events typically don’t require many getters. Additionally, a specific Event can only occur once. This implies that we can create an Order Entity with a particular Identity only once, and consequently, our code can only trigger the Event that describes the creation of that Order once. Any other Event related to that Order would be a different type of Event, pertaining to a distinct Order. Each Event essentially narrates something that has already taken place, representing the past. This means we trigger the OrderCreated Event after we have already created the Order, not before.\nGlobal Events\ntype Event interface { Name() string } type GeneralError string func (e GeneralError) Name() string { return \"event.general.error\" } Order Event\ntype OrderEvent interface { Event OrderID() uuid.UUID } type OrderDispatched struct { orderID uuid.UUID } func (e OrderDispatched) Name() string { return \"event.order.dispatched\" } func (e OrderDispatched) OrderID() uuid.UUID { return e.orderID } type OrderDelivered struct { orderID uuid.UUID } func (e OrderDelivered) Name() string { return \"event.order.delivery.success\" } func (e OrderDelivered) OrderID() uuid.UUID { return e.orderID } type OrderDeliveryFailed struct { orderID uuid.UUID } func (e OrderDeliveryFailed) Name() string { return \"event.order.delivery.failed\" } func (e OrderDeliveryFailed) OrderID() uuid.UUID { return e.orderID } The code example provided above demonstrates simple Domain Events. This code represents just one of countless ways to implement them in Go. In certain situations, such as with GeneralError, I have employed straightforward strings as Event representations. However, there are instances when I’ve utilized more complex objects or extended the base Event interface with a more specific one to introduce additional methods, as seen with OrderEvent.\n","title":"Practical DDD in Golang: Domain Event","type":"article"},{"content":"After discussing Entity and Value Objects, I will now introduce the third member of the group of Domain-Modeling patterns in this article: Domain Service. Domain Service is perhaps the most misunderstood DDD pattern, with confusion stemming from various web frameworks. In many frameworks, a Service takes on a multitude of roles. It\u0026rsquo;s responsible for managing business logic, creating UI components such as form fields, handling sessions and HTTP requests, and sometimes even serving as a catch-all \u0026ldquo;utils\u0026rdquo; class or housing code that could belong to the simplest Value Object.\nHowever, almost none of the aforementioned examples should be a part of a Domain Service. In this article, I will strive to provide a clearer understanding of its purpose and proper usage.\nStateless # A critical rule for Domain Services is that they must NOT maintain any state.\nAdditionally, a Domain Service must NOT possess any fields that have a state.\nWhile this rule may seem obvious, it\u0026rsquo;s worth emphasizing because it\u0026rsquo;s not always followed. Depending on a developer\u0026rsquo;s background, they may have experience in web development with languages that run isolated processes for each request. In such cases, it may not have been a concern if a Service contained state. However, when working with Go, it\u0026rsquo;s common to use a single instance of a Domain Service for the entire application. Therefore, it\u0026rsquo;s essential to consider the consequences when multiple clients access the same value in memory.\nUse State in Entity\ntype Account struct { ID uint Person Person Wallets []Wallet } Use State in Value Object\ntype Money struct { Amount int Currency Currency } DON\u0026rsquo;T use State in Domain Service\ntype DefaultExchangeRateService struct { repository *ExchangeRateRepository useForceRefresh bool } type CasinoService struct { bonusRepository BonusRepository bonusFactory BonusFactory accountService AccountService } As evident in the example above, both Entity and Value Object retain states. An Entity can modify its state during runtime, while Value Objects always maintain the same state. When we require a new instance of a Value Object, we create a fresh one.\nIn contrast, a Domain Service does not house any stateful objects. It solely contains other stateless structures, such as Repositories, other Services, Factories, and configuration values. While it can initiate the creation or persistence of a state, it does not retain that state itself.\nA Wrong Approach\ntype TransactionService struct { bonusRepository BonusRepository result Money // field that contains state } func (s *TransactionService) Deposit(account Account, money Money) error { bonuses, err := s.bonusRepository.FindAllEligibleFor(account, money) if err != nil { return err } // // some code // s.result = s.result.Add(money) // changing state of service return nil } In the example above, the TransactionService maintains a stateful field in the form of the Money Value Object. Whenever we intend to make a new deposit, we execute the logic for applying Bonuses and then add it to the final result, which is a field inside the Service. This approach is incorrect because it results in the modification of the total whenever anyone makes a deposit. This is not the desired behavior; instead, we should keep the summarization per Account. To achieve this, we should return the calculation as the result of a method, as shown in the example below.\nThe Right Approach\ntype TransactionService struct { bonusRepository BonusRepository } func (s *TransactionService) Deposit(current Money, account Account, money Money) (Money, error) { bonuses, err := s.bonusRepository.FindAllEligibleFor(account, money) if err != nil { return Money{}, err } // // some code // return current.Add(money), nil // returning new value that represents new state } The new TransactionService always generates the latest calculations instead of storing them internally. Different users cannot share the same object in memory, and the Domain Service can once again act as a single instance. In this approach, the client of this Service is now responsible for maintaining the new result and updating it whenever a deposit occurs.\nIt represents behaviors # A Domain Service represents behaviors specific to the Problem Domain. It offers solutions for complex business invariants that cannot be neatly encapsulated within a single Entity or Value Object. Occasionally, a particular behavior may involve interactions with multiple Entities or Value Objects, making it challenging to determine which Entity should own that behavior. In such cases, a Domain Service comes to the rescue.\nIt\u0026rsquo;s essential to clarify that a Domain Service is not responsible for handling sessions or requests, has no knowledge of UI components, doesn\u0026rsquo;t execute database migrations, and doesn\u0026rsquo;t validate user input. Its sole role is to manage business logic within the domain.\nAn Example of a Domain Service\ntype ExchangeRateService interface { IsConversionPossible(from Currency, to Currency) bool Convert(to Currency, from Money) (Money, error) } type DefaultExchangeRateService struct { repository *ExchangeRateRepository } func NewExchangeRateService(repository *ExchangeRateRepository) ExchangeRateService { return \u0026amp;DefaultExchangeRateService{ repository: repository, } } func (s *DefaultExchangeRateService) IsConversionPossible(from Currency, to Currency) bool { var result bool // // some code // return result } func (s *DefaultExchangeRateService) Convert(to Currency, from Money) (Money, error) { var result Money // // some code // return result, nil } In the example above, we have the ExchangeRateService as an instance. Whenever I need to provide a stateless structure that I should inject into another object, I define an interface. This practice aids in unit testing. The ExchangeRateService is responsible for managing the entire business logic related to currency exchange. It includes the ExchangeRateRepository to retrieve all exchange rates, allowing it to perform conversions for any amount of money.\nAnother Example of a Domain Service\ntype TransactionService struct { bonusRepository BonusRepository accountService AccountService // // some other fields // } func (s *TransactionService) Deposit(account Account, money Money) error { bonuses, err := s.bonusRepository.FindAllEligibleFor(account, money) if err != nil { return err } // // some code // for _, bonus := range bonuses { err = bonus.Apply(\u0026amp;account) if err != nil { return err } } // // some code // err = s.accountService.Update(account) if err != nil { return err } return nil } As mentioned, a Domain Service encapsulates business invariants that are too intricate to be confined to a single Entity or Value Object. In the example above, the TransactionService manages the complex logic of applying Bonuses whenever a new deposit is made by an Account. Instead of compelling the Account or Bonus Entities to rely on each other, or worse yet, furnishing expected repositories or services to Entity methods, the more suitable approach is to create a Domain Service. This Service can encapsulate the entire business logic for applying Bonuses to any Account as needed.\nIt represents contracts # In some scenarios, our Bounded Context relies on others. A common example is a cluster of Microservices, where one Microservice accesses another via a REST API. Frequently, data obtained from an external API is vital for the primary Bounded Context to function. Therefore, within our domain layer, we should have access to that data. It\u0026rsquo;s imperative to maintain separation between our domain layer and technical intricacies. This means that incorporating integration with an external API or database directly into our business logic is considered a code smell.\nThis is where the Domain Service comes into play. In the domain layer, I always provide an Interface for the Service as a Contract for external integrations. We can then inject that interface throughout our business logic, while the actual implementation resides in the infrastructural layer.\nA Contract on the Domain Layer\ntype AccountService interface { Update(account Account) error } The Implementation on the Infrastructure Layer\ntype AccountAPIService struct { client *http.Client } func NewAccountService(client *http.Client) domain.AccountService { return \u0026amp;AccountAPIService{ client: client, } } func (s AccountAPIService) Update(account domain.Account) error { var request *http.Request // // some code // response, err := s.client.Do(request) if err != nil { return err } // // some code // return nil } In the example above, I have defined the AccountService Interface in the domain layer. It serves as a Contract that other Domain Services can utilize. However, the actual implementation is provided through AccountAPIService. AccountAPIService is responsible for sending HTTP requests to an external CRM system or to our internal Microservice, specifically designed for handling Accounts. This approach allows for flexibility, as we can create an alternative implementation of AccountService. For instance, we could develop an implementation that works with test Accounts from a file, suitable for an isolated testing environment.\nDomain Service Vs. other types of Services # Up to this point, it\u0026rsquo;s clear when and why we should provide a Domain Service. However, in some cases, it\u0026rsquo;s not immediately evident if a Service should also be considered a Domain Service or belong to a different layer. Infrastructural Services are typically the easiest to identify. They invariably encompass technical details, database integration, or interaction with external APIs. Often, they serve as concrete implementations of Interfaces from other layers.\nPresentational Services are also straightforward to recognize. They consistently involve logic related to UI components or the validation of user inputs, with Form Service being a typical example.\nThe challenge arises when distinguishing between Application and Domain Services. I have personally found it most challenging to differentiate between these two types. In my experience, I have primarily used Application Services for providing general logic for managing sessions or handling requests. They are also suitable for managing Authorization and Access Rights.\nAn Application Service\ntype AccountSessionService struct { accountService AccountService } func (s *AccountSessionService) GetAccount(session *sessions.Session) (*Account, error) { value, ok := session.Values[\u0026#34;accountID\u0026#34;] if !ok { return nil, errors.New(\u0026#34;there is no account in session\u0026#34;) } id, ok := value.(string) if !ok { return nil, errors.New(\u0026#34;invalid value for account ID in session\u0026#34;) } account, err := s.accountService.ByID(id) if err != nil { return nil, err } return account, nil } In numerous instances, I have employed an Application Service as a wrapping structure for a Domain Service. I adopted this approach whenever I needed to cache something within the session and utilize the Domain Service as a fallback for data retrieval. You can observe this approach in the example above. In this example, AccountSessionService serves as an Application Service, encompassing the functionality of the AccountService from the Domain Layer. Its responsibility is to retrieve a value from the session store and subsequently utilize it to retrieve Account details from the underlying Service.\nConclusion # A Domain Service is a stateless structure that encapsulates behaviors from the actual business domain. It interacts with various objects, such as Entities and Value Objects, to handle complex behaviors, especially those that don\u0026rsquo;t have a clear home within other objects. It\u0026rsquo;s important to note that a Domain Service shares only its name with Services from other layers, as its purpose and responsibilities are entirely distinct.\nA Domain Service is exclusively relevant to business logic and should remain detached from technical details, session management, handling requests, or any other application-specific concerns.\nUseful Resources # Martin Fowler Domain Language ","date":"17 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-domain-service/","section":"Articles","summary":"After discussing Entity and Value Objects, I will now introduce the third member of the group of Domain-Modeling patterns in this article: Domain Service. Domain Service is perhaps the most misunderstood DDD pattern, with confusion stemming from various web frameworks. In many frameworks, a Service takes on a multitude of roles. It’s responsible for managing business logic, creating UI components such as form fields, handling sessions and HTTP requests, and sometimes even serving as a catch-all “utils” class or housing code that could belong to the simplest Value Object.\nHowever, almost none of the aforementioned examples should be a part of a Domain Service. In this article, I will strive to provide a clearer understanding of its purpose and proper usage.\nStateless # A critical rule for Domain Services is that they must NOT maintain any state.\nAdditionally, a Domain Service must NOT possess any fields that have a state.\nWhile this rule may seem obvious, it’s worth emphasizing because it’s not always followed. Depending on a developer’s background, they may have experience in web development with languages that run isolated processes for each request. In such cases, it may not have been a concern if a Service contained state. However, when working with Go, it’s common to use a single instance of a Domain Service for the entire application. Therefore, it’s essential to consider the consequences when multiple clients access the same value in memory.\nUse State in Entity\ntype Account struct { ID uint Person Person Wallets []Wallet } Use State in Value Object\ntype Money struct { Amount int Currency Currency } DON’T use State in Domain Service\ntype DefaultExchangeRateService struct { repository *ExchangeRateRepository useForceRefresh bool } type CasinoService struct { bonusRepository BonusRepository bonusFactory BonusFactory accountService AccountService } As evident in the example above, both Entity and Value Object retain states. An Entity can modify its state during runtime, while Value Objects always maintain the same state. When we require a new instance of a Value Object, we create a fresh one.\nIn contrast, a Domain Service does not house any stateful objects. It solely contains other stateless structures, such as Repositories, other Services, Factories, and configuration values. While it can initiate the creation or persistence of a state, it does not retain that state itself.\n","title":"Practical DDD in Golang: Domain Service","type":"article"},{"content":"In the previous article, I attempted to provide insights into the Value Object design pattern and how we should apply it in Go. In this article, the narrative continues with the introduction of a design pattern called Entity. Many developers have heard about Entity countless times, even if they\u0026rsquo;ve never used the DDD approach. Examples can be found in PHP frameworks and Java. However, its role in DDD differs from its use elsewhere. Discovering its purpose in DDD marked a significant turning point for me. It seemed a bit unconventional, especially for someone with a background in PHP MVC frameworks, but today, the DDD approach appears more logical.\nIt is not part of ORM # As demonstrated in the examples for PHP and Java frameworks, the Entity often assumes the roles of various building blocks, ranging from Row Data Gateway to Active Record. Due to this, the Entity pattern is frequently misused. Its intended purpose is not to mirror the database schema but to encapsulate essential business logic. When I work on an application, my Entities do not necessarily replicate the database structure.\nIn terms of implementation, my first step is always to establish the domain layer. Here, I aim to consolidate the entire business logic, organized within Entities, Value Objects, and Services. Once I\u0026rsquo;ve completed and unit-tested the business logic, I proceed to create an infrastructural layer, incorporating technical details like database connections. As illustrated in the example below, we separate the Entity from its representation in the database. Objects that mirror database schemas are distinct, often resembling Data Transfer Objects or Data Access Objects.\nEntity inside the Domain Layer\ntype BankAccount struct { ID uint IsLocked bool Wallet Wallet Person Person } Repository interface inside the Domain Layer\n// Repository interface inside domain layer type BankAccountRepository interface { Get(ctx context.Context, ID uint) (*BankAccount, error) } Data Access Object inside the Infrastructure Layer\ntype BankAccountGorm struct { ID uint `gorm:\u0026#34;primaryKey;column:id\u0026#34;` IsLocked bool `gorm:\u0026#34;column:is_locked\u0026#34;` Amount int `gorm:\u0026#34;column:amount\u0026#34;` CurrencyID uint `gorm:\u0026#34;column:currency_id\u0026#34;` Currency CurrencyGorm `gorm:\u0026#34;foreignKey:CurrencyID\u0026#34;` PersonID uint `gorm:\u0026#34;column:person_id\u0026#34;` Person PersonGorm `gorm:\u0026#34;foreignKey:PersonID\u0026#34;` } Concrete Repository inside the Infrastructure Layer\ntype BankAccountRepository struct { // // some fields // } func (r *BankAccountRepository) Get(ctx context.Context, ID uint) (*domain.BankAccount, error) { var dto BankAccountGorm // // some code // return \u0026amp;BankAccount{ ID: dto.ID, IsLocked: dto.IsLocked, Wallet: domain.Wallet{ Amount: dto.Amount, Currency: dto.Currency.ToEntity(), }, Person: dto.Person.ToEntity(), }, nil } The example shown above is just one of the many variations we can implement. While the structure of both the Entity and DTO can vary depending on the specific business case (such as having multiple Wallets per BankAccount), the core concept remains consistent.\nWe always maintain the Repository interface in the domain layer. Within this layer (which is typically the lowest one in the layered architecture I use), certain Domain Services may depend on Repositories, so they should be aware of their existence. Repositories provide a contract that ensures we work with Entity objects from our domain layer, at least when dealing with them externally. Inside the Repository, we can handle things as needed, as long as we deliver accurate results.\nWith this structure, I\u0026rsquo;ve consistently managed to separate my business logic from the underlying storage. When it comes to making changes to the database, only the mapping methods, which transform DTOs to Entities and vice versa, need to be modified.\nAdditional examples of Entities\ntype Currency struct { ID uint Code string Name string HtmlCode string } type Person struct { ID uint FirstName string LastName string DateOfBirth time.Time } type BankAccount struct { ID uint IsLocked bool Wallet Wallet Person Person } In some instances, Entities might encompass intricate business logic, drawing data from various sources such as relational databases, NoSQL databases, and external APIs. Particularly in these scenarios, the concept of segregating the business layer from technical details proves to be extremely beneficial.\nIdentity # The primary distinction from Value Objects is the concept of Identity. Entities possess Identities, which is their sole property that can establish their uniqueness. Even if two Entities differ slightly in one or more of their fields, they are considered the same Entity if they share the same Identity. Therefore, when we assess their equality, we solely examine their Identities.\nChecking Equality in Entity\ntype Currency struct { ID uint Code string Name string HtmlCode string } func (c Currency) IsEqual(other Currency) bool { return other.ID == c.ID } There are three types of Identities:\nApplication-generated Identities: In this case, we create new Identities for entities before they are stored in the database. UUIDs are commonly used for this purpose.\nNatural Identities: These involve using existing biological or unique identifiers when working with real-world entities, such as Social Security Numbers.\nDatabase-generated Identities: This is the most common approach, even when the option to implement the previous two solutions is available. In this approach, Identities are generated by the database.\nApplication-generated Identities\ntype Currency struct { ID uuid.UUID Code string Name string HtmlCode string } func NewCurrency() Currency { return Currency{ ID: uuid.New(), // generate new UUID } } Natural Identities\ntype Person struct { SSN string // social security number FirstName string LastName string DateOfBirth time.Time } Database-generated Identities\ntype BankAccount struct { ID uint IsLocked bool Wallet Wallet Person Person } type BankAccountGorm struct { ID uint `gorm:\u0026#34;primaryKey;autoIncrement:true\u0026#34;` IsLocked bool Amount int CurrencyID uint PersonID uint } I prefer to use only numerical values for indexing and querying. In many cases, when working with application-generated keys or natural keys, we encounter text-based data and need to find a way to accurately map these texts to numerical values in a database.\nSince Identity is the primary distinction between Entity and Value Object, you might guess that this line of separation can be easily blurred. Indeed, depending on the Bounded Context, an object can easily transition from being an Entity to a Value Object.\nTransaction Service\ntype Currency struct { ID uint Code string Name string HtmlCode string } Web Service\ntype Currency struct { Name string HtmlCode string } Just as seen in the example above, Currency can function as a central Entity within a specific Bounded Context, such as a Transaction Service or Exchange Service. However, in situations where we require it for UI formatting, Currency can be employed as a straightforward Value Object.\nValidation # In contrast to a Value Object, an Entity can alter its state over time. This implies that we need to perform ongoing validation checks whenever we intend to modify an Entity.\nValidation with each change\ntype BankAccount struct { ID uint IsLocked bool Wallet Wallet // // some fields // } func (ba *BankAccount) Add(other Wallet) error { if ba.IsLocked { return errors.New(\u0026#34;account is locked\u0026#34;) } // // do something // } Yes, I understand. In the example above, we can directly access Wallet and modify it without using the Add method. Personally, I\u0026rsquo;m not a big fan of Getters and Setters in Go. I find it hard to maintain when there are many functions that either return or set values. In such cases, I trust the engineers\u0026rsquo; judgment to understand how they should change the state of the Entity if methods are already available. However, I leave this decision to each developer to make on their own. Using getters and setters with private fields is also a viable solution.\nPushing behaviors # The primary goal of DDD is to closely mirror the business processes. Therefore, it shouldn\u0026rsquo;t come as a surprise when we encounter numerous methods within our domain layer. These methods can belong to various objects. Since Entities hold the most intricate state compared to all other code components, they may also feature the most functions to represent their extensive behaviors.\nIn some instances, we might observe that a couple of fields within an Entity consistently interact with each other. If we use one of them to enforce a particular business rule, it\u0026rsquo;s likely that we\u0026rsquo;ll also need the other one. In such cases, we can always group these fields into a single unit, a Value Object, and delegate its management to the Entity. However, we must approach this carefully to ensure a clear separation of concerns between the Entity and Value Objects.\nOne Wrong Approach\ntype Wallet struct { Amount int Currency Currency } type BankAccount struct { ID uint IsLocked bool Wallet Wallet // // some fields // } func (ba *BankAccount) Deduct(other Wallet) error { if ba.IsLocked { return errors.New(\u0026#34;account is locked\u0026#34;) } if !other.Currency.IsEqual(ba.Wallet.Currency) { return errors.New(\u0026#34;currencies must be the same\u0026#34;) } if other.Amount \u0026gt; ba.Wallet.Amount { return errors.New(\u0026#34;insufficient funds\u0026#34;) } ba.Wallet = Wallet{ Amount: ba.Wallet.Amount - other.Amount, Currency: ba.Wallet.Currency, } return nil } In the example above, we can see that the BankAccount Entity takes on more responsibility from the Wallet Value Object. It\u0026rsquo;s clear when we check if the BankAccount is locked or not. However, verifying the equality of Currency and ensuring there\u0026rsquo;s enough amount in the Wallet raises a code smell. In such situations, I relocate the entire deduction logic to the Value Object, except for the crucial task of verifying if the BankAccount is locked. This way, the Wallet gets its share of code to validate and deduct the amount.\nThe Right Approach\ntype Wallet struct { Amount int Currency Currency } func (w Wallet) Deduct(other Wallet) (*Wallet, error) { if !other.Currency.IsEqual(w.Currency) { return nil, errors.New(\u0026#34;currencies must be the same\u0026#34;) } if other.Amount \u0026gt; w.Amount { return nil, errors.New(\u0026#34;insufficient funds\u0026#34;) } return \u0026amp;Wallet{ Amount: w.Amount - other.Amount, Currency: w.Currency, }, nil } type BankAccount struct { ID uint IsLocked bool Wallet Wallet // // some fields // } func (ba *BankAccount) Deduct(other Wallet) error { if ba.IsLocked { return errors.New(\u0026#34;account is locked\u0026#34;) } result, err := ba.Wallet.Deduct(other) if err != nil { return err } ba.Wallet = *result return nil } This way, the Wallet Value Object can be associated with any other Entity or Value Object and still facilitate deductions based on its internal state. Conversely, the BankAccount can offer an additional method for deducting amounts from locked accounts without duplicating the same logic. An Entity has the flexibility to delegate its behaviors to other building blocks, such as Domain Services.\nI transfer these methods to Services in two scenarios. The first situation arises when the behavior is too intricate, possibly involving interactions with Specifications, Policies, other Entities, or Value Objects. It might also rely on results obtained from Repositories or other Services. The second case involves behaviors that aren\u0026rsquo;t overly complex but lack a clear place to reside. They could potentially belong to one Entity, another Entity, or even a Value Object.\nAnother Wrong Approach\ntype Currency struct { ID uint // // some fields // } type ExchangeRatesService struct { repository ExchangeRatesRepository } func (s *ExchangeRatesService) Exchange(to Currency, other Wallet) (Wallet, error) { // // do something // } When the business logic becomes too complex, my practice is to transfer it to a distinct Domain Service, as shown with the ExchangeRatesService in the example above. This approach has consistently allowed me to enhance my domain layer by introducing new Domain Policies.\nAt times, it seems like the right course of action to delegate behavior to other building blocks. However, it\u0026rsquo;s crucial to exercise caution when doing so. Transferring too many behaviors from Entities to Domain Services can give rise to another code smell known as the Anemic Domain Model.\nAnother Right Approach\ntype TransactionService struct { // // some fields // } func (s *TransactionService) Add(account *BankAccount, second Wallet) error { // // do something // } The example above illustrates the TransactionService Domain Service, which assumes responsibility from the BankAccount Entity. When there\u0026rsquo;s no need to validate complex business invariants, this behavior doesn\u0026rsquo;t necessarily belong in a Domain Service. Determining the appropriate location for a specific behavior is akin to an exercise that may appear challenging at first but becomes more intuitive with practice. Even today, I occasionally face difficulties in pinpointing the ideal location, but more often than not, I can structure the code as it should be.\nConclusion # While we commonly utilize them in various frameworks, it\u0026rsquo;s not always the best practice. Their role should be to represent our states and behaviors rather than merely mirroring the database schema. Entities provide us with valuable means to describe stateful real-world objects. In many instances, they serve as the core components of our applications, if not essential for our business logic to function properly.\nUseful Resources # Martin Fowler Domain Language ","date":"17 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-entity/","section":"Articles","summary":"In the previous article, I attempted to provide insights into the Value Object design pattern and how we should apply it in Go. In this article, the narrative continues with the introduction of a design pattern called Entity. Many developers have heard about Entity countless times, even if they’ve never used the DDD approach. Examples can be found in PHP frameworks and Java. However, its role in DDD differs from its use elsewhere. Discovering its purpose in DDD marked a significant turning point for me. It seemed a bit unconventional, especially for someone with a background in PHP MVC frameworks, but today, the DDD approach appears more logical.\nIt is not part of ORM # As demonstrated in the examples for PHP and Java frameworks, the Entity often assumes the roles of various building blocks, ranging from Row Data Gateway to Active Record. Due to this, the Entity pattern is frequently misused. Its intended purpose is not to mirror the database schema but to encapsulate essential business logic. When I work on an application, my Entities do not necessarily replicate the database structure.\nIn terms of implementation, my first step is always to establish the domain layer. Here, I aim to consolidate the entire business logic, organized within Entities, Value Objects, and Services. Once I’ve completed and unit-tested the business logic, I proceed to create an infrastructural layer, incorporating technical details like database connections. As illustrated in the example below, we separate the Entity from its representation in the database. Objects that mirror database schemas are distinct, often resembling Data Transfer Objects or Data Access Objects.\nEntity inside the Domain Layer\ntype BankAccount struct { ID uint IsLocked bool Wallet Wallet Person Person } Repository interface inside the Domain Layer\n// Repository interface inside domain layer type BankAccountRepository interface { Get(ctx context.Context, ID uint) (*BankAccount, error) } Data Access Object inside the Infrastructure Layer\ntype BankAccountGorm struct { ID uint `gorm:\"primaryKey;column:id\"` IsLocked bool `gorm:\"column:is_locked\"` Amount int `gorm:\"column:amount\"` CurrencyID uint `gorm:\"column:currency_id\"` Currency CurrencyGorm `gorm:\"foreignKey:CurrencyID\"` PersonID uint `gorm:\"column:person_id\"` Person PersonGorm `gorm:\"foreignKey:PersonID\"` } Concrete Repository inside the Infrastructure Layer\ntype BankAccountRepository struct { // // some fields // } func (r *BankAccountRepository) Get(ctx context.Context, ID uint) (*domain.BankAccount, error) { var dto BankAccountGorm // // some code // return \u0026BankAccount{ ID: dto.ID, IsLocked: dto.IsLocked, Wallet: domain.Wallet{ Amount: dto.Amount, Currency: dto.Currency.ToEntity(), }, Person: dto.Person.ToEntity(), }, nil } The example shown above is just one of the many variations we can implement. While the structure of both the Entity and DTO can vary depending on the specific business case (such as having multiple Wallets per BankAccount), the core concept remains consistent.\n","title":"Practical DDD in Golang: Entity","type":"article"},{"content":"Saying that a particular pattern is the most important might seem like an exaggeration, but I wouldn\u0026rsquo;t even argue against it. The first time I encountered the concept of a Value Object was in Martin Fowler\u0026rsquo;s book. At that time, it seemed quite simple and not very interesting. The next time I read about it was in Eric Evans\u0026rsquo; \u0026ldquo;The Big Blue Book.\u0026rdquo; At that point, the pattern started to make more and more sense, and soon enough, I couldn\u0026rsquo;t imagine writing my code without incorporating Value Objects extensively.\nSimple but beautiful # At first glance, a Value Object seems like a simple pattern. It gathers a few attributes into one unit, and this unit performs certain tasks. This unit represents a particular quality or quantity that exists in the real world and associates it with a more complex object. It provides distinct values or characteristics. It could be something like a color or money (which is a type of Value Object), a phone number, or any other small object that offers value, as shown in the code block below.\nQuantity\ntype Money struct { Value float64 Currency Currency } func (m Money) ToHTML() string { returs fmt.Sprintf(`%.2f%s`, m.Value, m.Currency.HTML) } Quality\ntype Color struct { Red byte Green byte Blue byte } func (c Color) ToCSS() string { return fmt.Sprintf(`rgb(%d, %d, %d)`, c.Red, c.Green, c.Blue) } Type extension\ntype Salutation string func (s Salutation) IsPerson() bool { returs s != \u0026#34;company\u0026#34; } Logical Group\ntype Phone struct { CountryPrefix string AreaCode string Number string } func (p Phone) FullNumber() string { returs fmt.Sprintf(\u0026#34;%s %s %s\u0026#34;, p.CountryPrefix, p.AreaCode, p.Number) } In Golang, you can depict Value Objects by creating new structs or by enhancing certain basic types. In either scenario, the goal is to introduce specialized functionalities for that individual value or a set of values. Frequently, Value Objects can supply particular methods for formatting strings to determine how values should operate during JSON encoding or decoding. However, the primary purpose of these methods should be to maintain the business rules linked to that particular characteristic or quality in real life.\nIdentity and Equality # A Value Object lacks identity, and that\u0026rsquo;s its key distinction from the Entity pattern. The Entity pattern possesses an identity that distinguishes its uniqueness. If two Entities share the same identity, it implies they refer to the same objects. On the other hand, a Value Object lacks such identity. It only consists of fields that provide a more precise description of its value. To determine equality between two Value Objects, we must compare the equality of all their fields, as demonstrated in the code block below.\nColor Value Object\nfunc (c Color) EqualTo(other Color) bool { return c.Red == other.Red \u0026amp;\u0026amp; c.Green == other.Green \u0026amp;\u0026amp; c.Blue == other.Blue } Money Value Object\nfunc (m Money) EqualTo(other Money) bool { return m.Value == other.Value \u0026amp;\u0026amp; m.Currency.EqualTo(other.Currency) } Currency Entity\nfunc (c Currency) EqualTo(other Currency) bool { return c.ID.String() == other.ID.String() } In the example above, both the Money and Color structs have defined EqualTo methods that examine all their fields. However, Currency checks for equality based on their Identities, which in this example are UUIDs.\nAs you can see, a Value Object can also reference an Entity object, as is the case with Money and Currency here. It can also include smaller Value Objects, like the Coin struct, which comprises both Color and Money. Alternatively, it can define a slice to store a collection of Colors.\nAdditional Value Objects\ntype Coin struct { Value Money Color Color } type Colors []Color In one Bounded Context, we may have numerous Value Objects. However, some of them may actually serve as Entities within other Bounded Contexts. This is the case for Currency. In a basic Web Service scenario where we simply want to display money, we can treat Currency as a Value Object, tightly linked to our Money object, which we don\u0026rsquo;t intend to modify. On the other hand, in a Payment Service where we require real-time updates through an Exchange Service API, we need to use identities within the Domain Model. In this situation, we\u0026rsquo;ll have distinct implementations of Currency in different services.\nWeb Service\ntype Currency struct { Code string HTML int } Payment Service\ntype Currency struct { ID uuid.UUID Code string HTML int } The choice of whether to use the Value Object or Entity pattern solely depends on what the object signifies within the Bounded Context. If it\u0026rsquo;s an object that can be reused, stored independently in the database, can undergo changes and be applied to multiple other objects, or is linked to an external Entity that must change whenever the external one changes, we refer to it as an Entity. However, if an object represents a value, is associated with a specific Entity, is essentially a direct copy from an external service, or should not exist independently in the database, then it qualifies as a Value Object.\nExplicitness # The most valuable aspect of a Value Object is its clarity. It offers transparency to the outside world, especially in situations where the default types in Golang (or any other programming language) do not support specific behavior or where the supported behavior is not intuitive. For example, when dealing with a customer across various projects that need to adhere to certain business rules, such as being an adult or representing a legal entity, using more explicit types like Birthday and LegalForm is a valid approach.\nBirthday Value Object\ntype Birthday time.Time func (b Birthday) IsYoungerThen(other time.Time) bool { return time.Time(b).After(other) } func (b Birthday) IsAdult() bool { return time.Time(b).AddDate(18, 0, 0).Before(time.Now()) } LegalForm Value Object\nconst ( Freelancer = iota Partnership LLC Corporation ) type LegalForm int func (s LegalForm) IsIndividual() bool { return s == Freelancer } func (s LegalForm) HasLimitedResponsability() bool { return s == LLC || s == Corporation } Sometimes, a Value Object doesn\u0026rsquo;t necessarily have to be explicitly designated as part of another Entity or Value Object. Instead, we can define a Value Object as a helper object that enhances clarity for future use in the code. This situation arises when dealing with a Customer who can either be a Person or a Company. Depending on the Customer\u0026rsquo;s type, the application follows different pathways. One of the more effective approaches could involve transforming customers to simplify handling them.\nValue Objects\ntype Person struct { FullName string Birthday Birthday } type Company struct { Name string CreationDate time.Time } Customer Entity\ntype Customer struct { ID uuid.UUID Name string LegalForm LegalForm Date time.Time } func (c Customer) ToPerson() Person { return Person{ FullName: c.Name, Birthday: c.Date, } } func (c Customer) ToCompany() Company { return Company{ Name: c.Name, CreationDate: c.Date, } } While cases involving transformations may occur in some projects, in the majority of situations, they indicate that we should include these Value Objects as an integral part of our Domain Model. In fact, when we observe that a specific subset of fields consistently interact with each other, even though they are part of a larger group, it\u0026rsquo;s a clear signal that we should group them into a Value Object. This allows us to use them as a single unit within our larger group, effectively making the larger group smaller in scope.\nImmutability # Value Objects are immutable. There is no single cause, reason, or argument to alter the state of a Value Object throughout its existence. Occasionally, multiple objects may share the same Value Object (though this isn\u0026rsquo;t a perfect solution). In such cases, we certainly don\u0026rsquo;t want to modify Value Objects in unexpected locations. Therefore, whenever we intend to modify an internal state of a Value Object or combine multiple of them, we must always return a new instance with the updated state, as shown in the code block below.\nA Wrong Approach\nfunc (m *Money) AddAmount(amount float64) { m.Amount += amount } func (m *Money) Deduct(other Money) { m.Amount -= other.Amount } func (c *Color) KeppOnlyGreen() { c.Red = 0 c.Bed = 0 } The Right Approach\nfunc (m Money) WithAmount(amount float64) Money { return Money { Amount: m.Amount + amount, Currency: m.Currency, } } func (m Money) DeductedWith(other Money) Money { return Money { Amount: m.Amount - other.Amount, Currency: m.Currency, } } func (c Color) WithOnlyGreen() Color { return Color { Red: 0, Green: c.Green, Blue: 0, } } In all examples, the correct approach is to consistently return new instances and leave the old ones unchanged. In Golang, it\u0026rsquo;s a best practice to associate functions with values rather than references to Value Objects, ensuring that we never modify their internal state.\nThe Right Approach with Validation\nfunc (m Money) Deduct(other Money) (*Money, error) { if !m.Currency.EqualTo(other.Currency) { return nil, errors.New(\u0026#34;currencies must be identical\u0026#34;) } if other.Amount \u0026gt; m.Amount { return nil, errors.New(\u0026#34;there is not enough amount to deduct\u0026#34;) } return \u0026amp;Money { Amount: m.Amount - other.Amount, Currency: m.Currency, }, nil } This immutability implies that we shouldn\u0026rsquo;t validate a Value Object throughout its entire existence. Instead, we should validate it only during its creation, as demonstrated in the example above. When creating a new Value Object, we should always carry out validation and return errors if business invariants are not met. If the Value Object passes validation, we can create it. After that point, there is no need to validate the Value Object anymore.\nRich behavior # A Value Object offers a variety of different behaviors. Its primary role is to furnish a rich interface. If it lacks methods, we should question its purpose and whether it truly serves any meaningful role. When a Value Object does make sense within a specific part of the code, it brings a substantial set of additional business invariants that more effectively describe the problem we aim to address.\nColor Value Object\nfunc (c Color) ToBrighter() Color { return Color { Red: math.Min(255, c.Red + 10), Green: math.Min(255, c.Green + 10), Blue: math.Min(255, c.Blue + 10), } } func (c Color) ToDarker() Color { return Color { Red: math.Max(0, c.Red - 10), Green: math.Max(0, c.Green - 10), Blue: math.Max(0, c.Blue - 10), } } func (c Color) Combine(other Color) Color { return Color { Red: math.Min(255, c.Red + other.Red), Green: math.Min(255, c.Green + other.Green), Blue: math.Min(255, c.Blue + other.Blue), } } func (c Color) IsRed() bool { return c.Red == 255 \u0026amp;\u0026amp; c.Green == 0 \u0026amp;\u0026amp; c.Blue == 0 } func (c Color) IsYellow() bool { return c.Red == 255 \u0026amp;\u0026amp; c.Green == 255 \u0026amp;\u0026amp; c.Blue == 0 } func (c Color) IsMagenta() bool { return c.Red == 255 \u0026amp;\u0026amp; c.Green == 0 \u0026amp;\u0026amp; c.Blue == 255 } func (c Color) ToCSS() string { return fmt.Sprintf(`rgb(%d, %d, %d)`, c.Red, c.Green, c.Blue) } Breaking down the entire Domain Model into smaller components like Value Objects (and Entities) clarifies the code and aligns it with real-world business logic. Each Value Object can represent specific components and facilitate various functions similar to standard business processes. Ultimately, this simplifies the entire unit testing process and aids in addressing all possible scenarios.\nConclusion # The real world is full of various characteristics, qualities, and quantities. Since software applications aim to address real-world issues, the use of such descriptors is unavoidable. Value Objects are introduced as a solution to tackle this need for clarity in our business logic.\nUseful Resources # Martin Fowler Domain Language ","date":"16 September 2023","externalUrl":null,"permalink":"/article/golang/practical-ddd-value-object/","section":"Articles","summary":"Saying that a particular pattern is the most important might seem like an exaggeration, but I wouldn’t even argue against it. The first time I encountered the concept of a Value Object was in Martin Fowler’s book. At that time, it seemed quite simple and not very interesting. The next time I read about it was in Eric Evans’ “The Big Blue Book.” At that point, the pattern started to make more and more sense, and soon enough, I couldn’t imagine writing my code without incorporating Value Objects extensively.\nSimple but beautiful # At first glance, a Value Object seems like a simple pattern. It gathers a few attributes into one unit, and this unit performs certain tasks. This unit represents a particular quality or quantity that exists in the real world and associates it with a more complex object. It provides distinct values or characteristics. It could be something like a color or money (which is a type of Value Object), a phone number, or any other small object that offers value, as shown in the code block below.\nQuantity\ntype Money struct { Value float64 Currency Currency } func (m Money) ToHTML() string { returs fmt.Sprintf(`%.2f%s`, m.Value, m.Currency.HTML) } Quality\ntype Color struct { Red byte Green byte Blue byte } func (c Color) ToCSS() string { return fmt.Sprintf(`rgb(%d, %d, %d)`, c.Red, c.Green, c.Blue) } Type extension\ntype Salutation string func (s Salutation) IsPerson() bool { returs s != \"company\" } Logical Group\ntype Phone struct { CountryPrefix string AreaCode string Number string } func (p Phone) FullNumber() string { returs fmt.Sprintf(\"%s %s %s\", p.CountryPrefix, p.AreaCode, p.Number) } In Golang, you can depict Value Objects by creating new structs or by enhancing certain basic types. In either scenario, the goal is to introduce specialized functionalities for that individual value or a set of values. Frequently, Value Objects can supply particular methods for formatting strings to determine how values should operate during JSON encoding or decoding. However, the primary purpose of these methods should be to maintain the business rules linked to that particular characteristic or quality in real life.\nIdentity and Equality # A Value Object lacks identity, and that’s its key distinction from the Entity pattern. The Entity pattern possesses an identity that distinguishes its uniqueness. If two Entities share the same identity, it implies they refer to the same objects. On the other hand, a Value Object lacks such identity. It only consists of fields that provide a more precise description of its value. To determine equality between two Value Objects, we must compare the equality of all their fields, as demonstrated in the code block below.\n","title":"Practical DDD in Golang: Value Object","type":"article"},{"content":" Article Writing Rules # Before writing any article, read this file in full. Do not scan the entire content tree. Only read the input Marko provides and apply the rules below.\nThe produced draft will be polished by the technology-blog-writer agent. Focus on correctness and completeness — the agent handles voice, vocabulary, and style cleanup.\nHow to use Marko\u0026rsquo;s input # Marko provides a raw markdown file with:\nH2 headings (##) to declare sections Code blocks without fences — raw Go code Bullet points below each code block explaining what that code demonstrates Your job:\nWrite a short introduction (no H2 heading) — 2–4 paragraphs setting context. Include inline links to relevant Go standard library packages or official docs when referencing specific features. For each section (H2 heading Marko provided): write prose that expands the bullet points into full paragraphs. Each bullet point should become 2–4 sentences minimum — explain the point, give context for why it matters, and connect it to the surrounding code or concept. Do not list bullet points verbatim. Do not compress multiple bullet points into a single sentence. Wrap each raw code block with the correct markdown fence: ```go ... ``` Add a short descriptive label above each code block on its own line (e.g., **Client initialisation**, **Main loop**). Write ## Conclusion (3–5 sentences, no padding) and ## Useful Resources (2–5 links, never invented). Never invent technical claims or code examples Marko has not provided. Never add sections Marko has not declared with an H2.\nProject-specific conventions # Language naming\nAlways write \u0026ldquo;Go\u0026rdquo; when referring to the programming language — never \u0026ldquo;Golang\u0026rdquo; or \u0026ldquo;golang\u0026rdquo; Exception: tag names, package import paths, and URLs where \u0026ldquo;golang\u0026rdquo; is technically required Links\nLink to other articles on this blog on first mention: [Repository](/article/golang/practical-ddd-repository \u0026quot;Repository\u0026quot;) Link to external references inline on first mention: [Martin Fowler](https://martinfowler.com/ \u0026quot;Martin Fowler\u0026quot;) Link text is the concept name — never \u0026ldquo;click here\u0026rdquo; or \u0026ldquo;this article\u0026rdquo; Useful Resources section\n2–5 links, bulleted Always includes relevant references: official Go docs, relevant packages, GitHub repos, referenced external resources Format: - [Link text](url \u0026quot;Link text\u0026quot;) Never invent URLs Article length # DDD/architecture articles: 1500–2500 words Tutorial/standard library articles: 1000–2000 words Do not pad to hit a word count. Stop when the content is complete. Series handling # If the article is part of a series:\nAdd series and series_order to front matter Reference previous articles naturally in the opening: \u0026ldquo;In the previous article we covered\u0026hellip;\u0026rdquo; The conclusion may end with a forward-looking sentence about the next article in the series What to do if Marko\u0026rsquo;s bullet points are ambiguous # Make a reasonable interpretation based on the code examples provided. Flag the ambiguity at the end of the draft with a note: \u0026ldquo;NOTE FOR MARKO: [specific question]\u0026rdquo; — do not silently guess on technical matters.\n","externalUrl":null,"permalink":"/article/claude/","section":"Articles","summary":"Article Writing Rules # Before writing any article, read this file in full. Do not scan the entire content tree. Only read the input Marko provides and apply the rules below.\nThe produced draft will be polished by the technology-blog-writer agent. Focus on correctness and completeness — the agent handles voice, vocabulary, and style cleanup.\nHow to use Marko’s input # Marko provides a raw markdown file with:\nH2 headings (##) to declare sections Code blocks without fences — raw Go code Bullet points below each code block explaining what that code demonstrates Your job:\nWrite a short introduction (no H2 heading) — 2–4 paragraphs setting context. Include inline links to relevant Go standard library packages or official docs when referencing specific features. For each section (H2 heading Marko provided): write prose that expands the bullet points into full paragraphs. Each bullet point should become 2–4 sentences minimum — explain the point, give context for why it matters, and connect it to the surrounding code or concept. Do not list bullet points verbatim. Do not compress multiple bullet points into a single sentence. Wrap each raw code block with the correct markdown fence: ```go ... ``` Add a short descriptive label above each code block on its own line (e.g., **Client initialisation**, **Main loop**). Write ## Conclusion (3–5 sentences, no padding) and ## Useful Resources (2–5 links, never invented). Never invent technical claims or code examples Marko has not provided. Never add sections Marko has not declared with an H2.\nProject-specific conventions # Language naming\nAlways write “Go” when referring to the programming language — never “Golang” or “golang” Exception: tag names, package import paths, and URLs where “golang” is technically required Links\nLink to other articles on this blog on first mention: [Repository](/article/golang/practical-ddd-repository \"Repository\") Link to external references inline on first mention: [Martin Fowler](https://martinfowler.com/ \"Martin Fowler\") Link text is the concept name — never “click here” or “this article” Useful Resources section\n2–5 links, bulleted Always includes relevant references: official Go docs, relevant packages, GitHub repos, referenced external resources Format: - [Link text](url \"Link text\") Never invent URLs Article length # DDD/architecture articles: 1500–2500 words Tutorial/standard library articles: 1000–2000 words Do not pad to hit a word count. Stop when the content is complete. Series handling # If the article is part of a series:\n","title":"","type":"article"},{"content":"","externalUrl":null,"permalink":"/news/","section":"Ompluscator's Blog","summary":"","title":"","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"}]