(DOCS): Created the Project Structure guide.

This serves as outsider notes as well as my own reference sheet.
This commit is contained in:
Hayden Hargreaves 2025-06-13 14:59:39 -07:00
parent 9b7166f3b6
commit e8d4d8d8ab
7 changed files with 468 additions and 0 deletions

101
doc/ProjectStructure.md Normal file
View File

@ -0,0 +1,101 @@
# Project Structure
<!--toc:start-->
- [Project Structure](#project-structure)
- [Data Flow](#data-flow)
- [Directory Structure](#directory-structure)
- [Handler](#internalapphanders)
- [Services](#internalappservices)
- [Domain](#internaldomain)
- [Infrastructure](#internalinfrastructuredatabase)
- [Templates](#internaltemplates)
- [Static](#webstatic)
<!--toc:end-->
This document describes how the **Potion project** is build. Where each piece can
be found, and where new pieces should be built.
This project attempts to follow the **Domain Driven Design** (DDD) pattern which
means that the software should be written in a way that represents the business
requirements and domain. The goal is to produce software that is technically
sound but also a strong reflection of the business domain.
DDD implementation in this project means that each "domain" (users, recipes,
etc) are defined in their own domain segment and should be used throughout the
project.
## Data Flow
Data flows through a strict pipe line, as described bellow:
```md
**HTTP > handler > service > repository**
```
The data is first send from the client via **HTTP**. The data is routed to the
proper **handler** which will take control of request and eventually send the
response back to the client.
The handler will then call the proper **service** which will complete any business
logic required before sending the data to the respective **repository.** This
repository (which implemented the proper *domain* interface) will communicate with
the database and perform and database actions, finally returning the new data
back to the service.
Finally, the service will clean up the data and ensure the data fits into the
proper *domain* object and is then bubbled back up to the handler which sends the
response to the client.
## Directory Structure
The directory structure of this project intends to achieve the most efficient
structure while still meeting the goals defined above.
#### `internal/app/handers`
**Handlers** are responsible for receiving requests and sending back responses.
These strongly mimic the controller in the well-known **Model View Controller**
(MVC) architecture. Very little logic takes place here.
#### `internal/app/services`
**Services** are responsible for the majority of the software's business logic. When
a request is received by the handler, the service is called and executes all logic
required to achieve the end response. However, the service has no communication
and therefore relies on the repository for the database.
#### `internal/domain`
The **domain** is the definition of the data models and interfaces that are used by
the application. The data model of each domain object is defined here as well as
the respective service and repository interfaces are defined here. No logic will
be placed here, simply just definitions and interfaces.
#### `internal/infrastructure/database`
The **infrastructure** module is responsible for housing communication to outside
sources, such as the database. The database section (defined here) contains all
**migrations**: SQL definitions of database changes. It also contains all of the
**repository** implementations as defined by the domain interfaces.
#### `internal/templates`
**Templates** are HTML templates which are served to the user as the web UI. This
directory has a very module structure; being broken down into many categories:
- **Layouts:** Page layouts and other large wrappers.
- **Components:** Small scale components for use in various other templates.
- **Partials:** Smallest size elements: common buttons, headers, text elements.
- **Pages:** Individual pages which are composed and served to the user.
#### `web/static`
**Static** documents, elements, or other assets are stored in the web directory.
Items such as images, icons, style sheets, or even JS scripts can be stored here.
This directory will be hosted by the server as a static directory for access by
the UI.

45
examples/hello.templ Normal file
View File

@ -0,0 +1,45 @@
package main
script log(s string) {
console.log(s)
}
templ hello(name string) {
<div>Hello, { name }</div>
}
templ headerTemplate(name string) {
<header data-testid="headerTemplate">
<h1>{ name } </h1>
</header>
}
templ button(text string) {
<button onClick={log("clicked")} class="button">
{ text }
</button>
}
templ component(testID string) {
<p data-testid={ testID }>text </p>
@headerTemplate("Hayden")
}
templ page2() {
<p
if 5==5 {
class="also five"
}
>
if 5 == 5 {
"five is five"
}
</p>
@component("testid-123")
}
templ spread(attrs templ.Attributes) {
<p class="bg-red-500" { attrs... }>
Text
</p>
}

296
examples/hello_templ.go Normal file
View File

@ -0,0 +1,296 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.865
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func log(s string) templ.ComponentScript {
return templ.ComponentScript{
Name: `__templ_log_bfa8`,
Function: `function __templ_log_bfa8(s){console.log(s)
}`,
Call: templ.SafeScript(`__templ_log_bfa8`, s),
CallInline: templ.SafeScriptInline(`__templ_log_bfa8`, s),
}
}
func hello(name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div>Hello, ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 8, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func headerTemplate(name string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header data-testid=\"headerTemplate\"><h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 13, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</h1></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func button(text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, log("clicked"))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<button onClick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.ComponentScript = log("clicked")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" class=\"button\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 19, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func component(testID string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p data-testid=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(testID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `examples/hello.templ`, Line: 24, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">text </p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = headerTemplate("Hayden").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func page2() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<p")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if 5 == 5 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " class=\"also five\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if 5 == 5 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"five is five\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = component("testid-123").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func spread(attrs templ.Attributes) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p class=\"bg-red-500\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, attrs)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ">Text</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

19
examples/main.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"net/http"
"github.com/a-h/templ"
)
func main() {
component := page2()
btn := button("Click me")
http.Handle("/", templ.Handler(component))
http.Handle("/button", templ.Handler(btn))
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}

View File

@ -22,6 +22,8 @@
go-tools go-tools
htmx-lsp2 htmx-lsp2
templ templ
tailwindcss_4
tailwindcss-language-server
]; ];
# Define the shell that will be executed. # Define the shell that will be executed.

5
tailwind.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
content: ["./**/*.html", "./**/*.templ", "./**/*.go",],
theme: { extend: {}, },
plugins: [],
}