{"id":22496989,"url":"https://github.com/morebec/misas-go","last_synced_at":"2025-06-21T04:07:11.269Z","repository":{"id":61271636,"uuid":"527396166","full_name":"Morebec/misas-go","owner":"Morebec","description":"MISAS Go implementation","archived":false,"fork":false,"pushed_at":"2023-11-18T02:04:56.000Z","size":278,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2025-02-01T23:45:13.683Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Morebec.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-08-22T03:20:24.000Z","updated_at":"2022-10-11T19:23:21.000Z","dependencies_parsed_at":"2023-11-18T03:58:26.916Z","dependency_job_id":null,"html_url":"https://github.com/Morebec/misas-go","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morebec%2Fmisas-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morebec%2Fmisas-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morebec%2Fmisas-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morebec%2Fmisas-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Morebec","download_url":"https://codeload.github.com/Morebec/misas-go/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245925719,"owners_count":20694949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-06T20:15:12.931Z","updated_at":"2025-03-27T21:25:35.816Z","avatar_url":"https://github.com/Morebec.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MISAS Go\n\nMISAS Go is an opinionated library to easily develop systems using a predefined architecture using DDD, CQRS and ES.\nIt provides a solid base for smaller teams to develop advanced system with lesser means.\nIt is the go implementation of [MISAS](https://github.com/Morebec/misas).\n\n## Features\n- Domain-Driven Design\n- Event Sourcing\n- CQRS\n- Intra/Out of Process Messaging\n- Observability\n  - Tracing using Open Telemetry as automated instrumentation on Command/Query/Event/Prediction buses. \n  - Tracing using correlation ID and causation ID on messages propagated to events.\n\n## Introduction\nMISAS Go mostly provides a set of abstractions to implement DDD, CQRS and ES concepts according to [MISAS](https://github.com/Morebec/misas).\nIt also provides a few concrete implementation of these concepts, for the most common use cases.\n\n\n## Batteries included\nHere are some batteries that this library includes to make working with MISAS Systems in Go easier.\n\n- `clock`: Provides an abstraction of a clock.\nby returning \"REDACTED\" when converted to a string.\n- `postgresql/eventstore`: Implementation of an event store using PostgreSQL.\n- `postgresql/documentstore`: Implementation of a document store using PostgreSQL to easily persist unstructured data.\n- `postgresql/checkpointstore`: Implementation of a checkpoint store using PostgreSQL to persist the last processed event during event processing.\n- `postgresql/predictionstore`: Implementation of a prediction store using PostgreSQL.\n- `secret`: Provides a string implementation that avoids showing certain sensitive values in logs or external systems,\n\n\n### Defining a System\nAt the core of the library there is the concept of `System` which represents an information system.\nThe `System` struct is used as a centralized point to define systems.\nAlthough entirely optional, the use of the `System` allows to expressively define the dependencies\nof the core units within the system: \n\n\n```go\n\tutcClock := clock.NewUTCClock()\n\n\ts := system.New(\n\t\t// These information are reused in logs, tracing spans or as metadata for events.\n\t\tsystem.WithInformation(system.Information{\n\t\t\tName:    \"unit_test\",\n\t\t\tVersion: \"1.0.0\",\n\t\t}),\n\t\tsystem.WithEnvironment(system.Test),\n\t\tsystem.WithClock(utcClock),\n\n\t\tsystem.WithCommandHandling(\n\t\t\tsystem.WithCommandBus(\n\t\t\t\tcommand.NewInMemoryBus(),\n\t\t\t),\n\t\t),\n\n\t\tsystem.WithQueryHandling(\n\t\t\tsystem.WithQueryBus(\n\t\t\t\tquery.NewInMemoryBus(),\n\t\t\t),\n\t\t),\n\n\t\tsystem.WithEventHandling(\n\t\t\tsystem.WithEventBus(\n\t\t\t\tevent.NewInMemoryBus(),\n\t\t\t),\n\t\t\tsystem.WithEventStore(\n\t\t\t\tpostgresql.NewEventStore(\"connectionString\", utcClock),\n\t\t\t),\n\t\t),\n\n\t\tsystem.WithPredictionHandling(\n\t\t\tsystem.WithPredictionBus(\n\t\t\t\tprediction.NewInMemoryBus(),\n\t\t\t),\n\t\t\tsystem.WithPredictionStore(\n\t\t\t\tpostgresql.NewPredictionStore(),\n\t\t\t),\n\t\t),\n\n\t\tsystem.WithInstrumentation(\n\t\t\tsystem.WithTracer(instrumentation.NewSystemTracer()),\n\t\t\tsystem.WithDefaultLogger(),\n\t\t\tsystem.WithJaegerTracingSpanExporter(\"urlToJaeggerInstance\"),\n\t\t\tsystem.WithCommandBusInstrumentation(), // Decorates the command bus adding automated instrumentation.\n\t\t\tsystem.WithQueryBusInstrumentation(), // Decorates the query bus adding automated instrumentation.\n\t\t\tsystem.WithEventBusInstrumentation(), // Decorates the event bus adding automated instrumentation.\n\t\t\tsystem.WithPredictionBusInstrumentation(), // Decorates the prediction bus adding automated instrumentation.\n\t\t\tsystem.WithEventStoreInstrumentation(), // Decorates the event store adding automated instrumentation.\n\t\t),\n\n\t\t// Modules allow separating the dependencies of the systems.\n\t\tsystem.WithSubsystems(\n\t\t\tfunc(s *system.Subsystem) {\n\t\t\t\t// Registers\n\t\t\t\ts.RegisterEvent(accountCreated{})\n\t\t\t\ts.RegisterCommandHandler(createAccount{}, createAccountCommandHandler))\n\t\t\t}, \n\t\t),\n\t)\n\n\t// Entry points are procedure to start the system and its subsystem's interaction layers.\n\t// Depending on the needs of the system, one could need to define different entry points\n\t// starting different things. (e.g. Web Server, Message Queue etc.)\n    mainEntryPoint := NewEntryPoint(\n\t\t// Name of the entry point, if instrumentation is enabled (see below), this name will be used in spans.\n\t\t\"web_server\",\n\n\t\t// Function to effectively start the entry point.\n\t\tfunc(ctx context.Context, s *System) error {\n\t\t\treturn nil\n\t\t},\n\n\t\t// Function to stop the entry point.\n\t\tfunc(ctx context.Context, s *System) error {\n\t\t\treturn nil\n\t\t},\n\n\t\t// Allows adding automated instrumentation on the entry point.\n\t\tWithEntryPointInstrumentation(),\n\t),\n\t\n\t// Allows running the system with the given entry point.\n\tif err := s.RunEntryPoint(mainEntryPoint); err != nil {\n\t\tpanic(err)\n\t}\n```\n\n## Command Processing\n\n### Command Handlers \u0026 Failures\n\n### Registering a Command Handler with the System\n\n## Aggregates\n\n### Implementing an event sourced Aggregate\nThe aggregate interface has the following structure:\n```go\ntype User struct {\n\tEventSourcedAggregateBase\n\n\tID           string\n\tEmailAddress string\n}\n\nfunc (u *User) ApplyEvent(evt event.Event) {\n\tswitch evt.(type) {\n\tcase UserRegisteredEvent:\n\t\te := evt.(UserRegisteredEvent)\n\t\tu.ID = e.ID\n\t\tu.EmailAddress = e.EmailAddress\n\t}\n}\n\nfunc RegisterUser(id string, emailAddress string) *User {\n\tu := \u0026User{\n\t\tID:           \"\",\n\t\tEmailAddress: \"\",\n\t}\n\t// NOTE THIS LINE HERE\n\tu.EventSourcedAggregateBase = EventSourcedAggregateBase{\n\t\tApplyEvent: u.ApplyEvent,\n\t}\n\n\tu.RecordEvent(UserRegisteredEvent{\n\t\tID:           id,\n\t\tEmailAddress: emailAddress,\n\t})\n\n\treturn u\n}\n```\n\n### Aggregate Repositories\nIf using the  `EventSourcedAggregate` interface, one can use the `domain.EventStoreRepository` helper to quickly\nimplement event store based repositories for aggregates through composition:\n\n```go\ntype UserRepository struct {\n  inner: aggregate.EventStoreRepository\n}\n\nfunc NewUserRepository(es event.Store) *UserRepository {\n  return \u0026UserRepository{\n    inner: aggregate.NewEventStoreRepository(es, func() aggregate.Aggregate { \n\t  // This callback allows defining the initial state of an aggregate before applying its saved changes\n          // when loading.\n      return \u0026User{}\n    }),\n  }\n}\n\nfunc (r *UserRepository) Add(ctx context.Context, u *User) error {\n    return r.inner.Add(ctx, event.StreamID(\"user/\"+u.ID), u)\t\n}\n\nfunc (r *UserRepository) Save(ctx context.Context, u *User, version Version) error {\n    return r.inner.Save(ctx, event.StreamID(\"user/\"+u.ID), u, version)\t\n}\n\nfunc (r *UserRepository) FindByID(ctx context.Context, id UserID) (*User, Version, error) {\n  loaded, v, err := r.inner.Load(ctx, event.StreamID(\"user/\"+u.ID))\n  if err != nil {\n    return \u0026User{}, 0, err\n  }\n  \n  return loaded.(*User), v, nil\n}\n```\n\n## Domain Errors\nYou can create errors like so\n```go\nconst UserNotFoundErrorTypeName domain.ErrorTypeName = \"user_not_found\"\nfunc UserNotFoundError(id UserId, cause error) error\n\treturn domain.NewError(\n\t\tWithTypeName(UserNotFoundErrorTypeName),\n\t\tWithMessage(fmt.Sprtinf(\"user %s not found\", string(id))),\n\t\tWithCause(cause)\n\t\tWithData(map[string]any{\n\t\t\t\"id\": string(id)\n\t\t})\n\t)\n)\n```\n\nYou can test that an error is of a given typeName\n\n```go\ndomain.IsDomainErrorWithTypeName(UserNotFoundErrorTypeName)\n```\n\n\nDomain errors can also be tagged upon creation:\n```go\ndomain.NewError(\n\t// ...\n\tWithTag(\"a tag\")\n\tWithTags(\"another tag\", \"yet another tag\")\n)\n```\n\nTags allow grouping errors, for example a system might have a lot of different not found errors for specific\ntypes of resources, aggregates and views. Some components of the system might simply want to know if an error\nqualifies as a not found error without needing to maintain a list of all the `ErrorTypeNames` that qualifies for this.\n\nThis is where tags come into play. There are a few tags available out of the box:\n- `domain.NotFoundTag`: When a resource, aggregate, view etc. was not found.\n- `domain.AlreadyExistsTag`: When a resource, aggregate view, was expected not to be found.\n- `domain.ValidationErrorTag`: When an error represents a validation error.\n\n\n## Query Processing\n\n### Query Handlers \u0026 Failures\n\n## Event Processing\n\n\n### Registering a Query Handler with the System\n\n### Event Store\n\n### Event Store Subscriptions\n\n### Event Processors\n\n## Event Handlers\n```go\neventBus.RegisterHandler(EventTypeName, Handler)\n```\n\n### Checkpoints\n\n\n### Event Handlers \u0026 Failures\nWhen an event handler fails to process an event, there are two common strategies at our disposal:\n- **Continued Processing:** Ignore/log the failure and continue processing the next events.\n- **Delayed Processing:** Stop/retry the processing at the problematic event, until fixed.\n\nEach strategy has its own pros and cons. \n**Delayed Processing** prevents any out-of-order processing of events, and ensures that the system when done with the processing will be fully consistent.\nHowever, it will require the \nevent handlers to be idempotent since they have the potential of being called multiple times for the same events in cases of retries.\n**Continued Processing** has the benefit of not blocking the processing of events and can therefore minimize the impact it has on other components of the system,\nhowever, it also means that event handlers should be implemented in a way to support inconsistencies in data since some events will have happened and will hve been partially\napplied. This leads to a system that can be slightly inconsistent, and will require close attention to these potential inconsistencies.\n\nAn interesting strategy is to used Delayed processing combined with event processing partitions, \n(e.g. one event processor per subsystem) which can often drastically minimize the bottlenecks occasioned by having a problematic event.\n\n## Registering Event Handlers with the System\n\n## Prediction Processing\n\n### Prediction Handlers \u0026 Failures\n\n\n## Projection Processing\n\n### Projectors \u0026 Failures\n\n\n### HTTP API\n\n#### Define an Endpoint\n```go\nfunc HomeEndpoint(r chi.Router) {\n\tr.Get(\"/\", func(writer http.ResponseWriter, request *http.Request) {\n\t\twriter.WriteHeader(500)\n\t\trender.JSON(writer, request, NewSuccessResponse(nil))\n\t})\n}\n```\n\n#### Start HTTP Web Server\n```go\nfunc StartFrontendAPI() error {\n\tr := chi.NewRouter()\n\tr.Use(middleware.AllowContentType(\"application/json\"))\n\tr.Use(middleware.CleanPath)\n\tr.Use(middleware.RealIP)\n\tr.Use(middleware.Recoverer)\n\n\tr.Use(middleware.Timeout(time.Second * 60))\n\tr.Use(render.SetContentType(render.ContentTypeJSON))\n\n\tHomeEndpoint(r)\n\n\tif err := http.ListenAndServe(\":3000\", r); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorebec%2Fmisas-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorebec%2Fmisas-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorebec%2Fmisas-go/lists"}