feat(11.1-03): add the Architecture and Plugins docs sections

- architecture: introduction, Go modules and workspaces, application
  lifecycle, request lifecycle
- plugins: registration, scheduling, extending, testing
- verified Examples for backpack services, towel request context,
  pact schedules and festival events
- TestDocsRequiredPages lists the eight new pages
This commit is contained in:
Jakub Zych
2026-09-30 22:12:08 +02:00
parent d6003cd84b
commit 1f8f5e1b51
14 changed files with 749 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
package backpack_test
import (
"fmt"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
)
// Greeter is a service the acme.greeter plugin offers to other plugins.
type Greeter interface {
Greet(name string) string
}
type englishGreeter struct{}
func (englishGreeter) Greet(name string) string { return "Hello, " + name }
func ExampleApp_Publish() {
app := backpack.New(&compass.Config{})
app.SetPlugins([]string{"acme.greeter", "acme.blog"})
// acme.greeter, in its Register step: publish under the interface type.
var greeter Greeter = englishGreeter{}
if err := app.Publish(greeter); err != nil {
fmt.Println(err)
return
}
// acme.blog, in its Boot step: look the service up by the same type.
if app.HasPlugin("acme.greeter") {
if found, ok := app.Lookup[Greeter](); ok {
fmt.Println(found.Greet("blog"))
}
}
// A second Publish under the same type is refused.
fmt.Println(app.Publish(greeter) != nil)
// Output:
// Hello, blog
// true
}

View File

@@ -0,0 +1,35 @@
package festival_test
import (
"context"
"fmt"
"git.golem15.com/golem15/summercms/modules/festival"
)
// PostPublished is the event the acme.blog plugin fires after a post goes live.
type PostPublished struct {
Title string
}
func ExampleBus_Fire() {
bus := festival.New() // in a plugin, use app.Events
// acme.search and acme.notify extend acme.blog from their Boot steps.
bus.Listen("acme.search", func(ctx context.Context, e PostPublished) error {
fmt.Println("index", e.Title)
return nil
})
bus.ListenPriority("acme.notify", 10, func(ctx context.Context, e PostPublished) error {
fmt.Println("notify subscribers of", e.Title)
return nil
})
// acme.blog fires the event; higher priorities run first.
if err := bus.Fire(context.Background(), PostPublished{Title: "Hello"}); err != nil {
fmt.Println(err)
}
// Output:
// notify subscribers of Hello
// index Hello
}

View File

@@ -0,0 +1,37 @@
package pact_test
import (
"fmt"
"time"
"git.golem15.com/golem15/summercms/modules/pact"
)
// BlogPlugin is the acme.blog plugin; only its schedule is shown here.
type BlogPlugin struct{}
var _ pact.HasSchedule = (*BlogPlugin)(nil)
// Schedule runs three of the plugin's registered console commands.
func (p *BlogPlugin) Schedule() []pact.ScheduledCommand {
return []pact.ScheduledCommand{
{Command: "blog:prune-drafts", Cadence: pact.Daily()},
{Command: "blog:send-digest", Cadence: pact.DailyAt(7, 30)},
{Command: "blog:sync-feed", Args: []string{"--quiet"}, Cadence: pact.Every(15 * time.Minute)},
}
}
func ExampleHasSchedule() {
var plugin pact.HasSchedule = &BlogPlugin{}
for _, entry := range plugin.Schedule() {
if hour, minute, daily := entry.Cadence.At(); daily {
fmt.Printf("%s %v: daily at %02d:%02d\n", entry.Command, entry.Args, hour, minute)
continue
}
fmt.Printf("%s %v: every %s\n", entry.Command, entry.Args, entry.Cadence.Interval())
}
// Output:
// blog:prune-drafts []: daily at 00:00
// blog:send-digest []: daily at 07:30
// blog:sync-feed [--quiet]: every 15m0s
}

View File

@@ -0,0 +1,37 @@
package towel_test
import (
"fmt"
"net/http"
"net/http/httptest"
"git.golem15.com/golem15/summercms/modules/towel"
)
func ExampleWithLocale() {
// A middleware stores the organization once for the whole request.
withAcme := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := towel.WithOrganization(r.Context(), "acme")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// The handler reads the values back from its request context.
listPosts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
org, _ := towel.Organization(r.Context())
locale, ok := towel.Locale(r.Context())
if !ok {
locale = "en"
}
fmt.Fprintf(w, "posts for %s in %s", org, locale)
})
// surf sets the locale from Accept-Language; here the test sets it.
req := httptest.NewRequest(http.MethodGet, "/api/blog/posts", nil)
req = req.WithContext(towel.WithLocale(req.Context(), "pl"))
rec := httptest.NewRecorder()
withAcme(listPosts).ServeHTTP(rec, req)
fmt.Println(rec.Body.String())
// Output: posts for acme in pl
}