Skip to content

Latest commit

 

History

History
123 lines (101 loc) · 4.58 KB

structure.md

File metadata and controls

123 lines (101 loc) · 4.58 KB

Project Structure

Epigram is based on a hexagonal architecture (also known as ports and adapters), which permits swapping both the database / storage infrastructure and the front end presentation layer with minimal changes to the codebase and preservation of the core application logic.

To accomplish this, the application is broken down into three primary tiers:

  • server
    • Provides an interface to the services for client interaction.
    • Currently implemented adapters:
      • http - uses Go templating to perform server side rendering and provide an interface to the application from a browser.
  • service
    • Core application logic.
    • Shared across all server and storage implementation.
    • Each service requires a unique repository to maintain on it's data.
    • Some services interface with sub-services, and abstract their methods for use by the server.
  • storage
    • Application state storage.
    • Comprised of multiple repositories, each one tasked with storing a specific, consistent type of data.
    • Currently implemented adapters:
      • inmemory - stores all data in maps in memory, useful for testing.
      • sqlite - uses statically-linked sqlite3 library to store data in local database file.

In addition, each layer depends on the model package, which holds all the structure definitions for objects used throughout the above three tiers.

The below diagram shows how a server implementation utilizes the top level service entities, and how each service interacts with others and their repositories in the storage package.

classDiagram
    %%class `model.User` {
    %%    +ID         string
    %%    +Name       string
    %%    +Email      string
    %%    +PictureURL string
    %%    +Created    time.Time
    %%    +QuizPassed   bool
    %%    +QuizAttempts int8
    %%    +Banned       bool
    %%    +Admin        bool
    %%    -isAuthorized() bool
    %%    -isAdmin() bool
    %%}

    class `service.User` {
        -ur UserRepository
        -sess service.UserSession
        +GetUserFromIDToken(ctx context.Context, token oidc.IDToken) (model.User, error)
        +CreateUser(ctx context.Context, u *model.User) error
        +FindUserById(ctx context.Context, id string) (model.User, error)
        +UpdateUser(ctx context.Context, u model.User) error
        +CreateUserSession(ctx context.Context, u model.User) (model.UserSession, error)
        +GetUserFromSessionID(ctx context.Context, sessID string) (model.User, error)
    }

    class `service.UserSession` {
        -repo UserSessionRepository
        +CreateUserSession(ctx context.Context, u model.User) (model.UserSession, error)
        +FindSessionByID(ctx context.Context, id string) (model.UserSession, error)
    }

    `service.User` --> `service.UserSession`

    class `service.EntryQuiz`{
        +Questions []QuizQuestion
        +VerifyAnswers(answers map[int]string) (passed bool)
    }
    
    class `server`{

    }

    `server` --> `service.User`
    `server` --> `service.EntryQuiz`

    `service.User` --> `UserRepository`

    class `UserRepository` {
        <<Interface>>
        +Create(ctx context.Context, u model.User) error
        +Update(ctx context.Context, u model.User) error
        +FindByID(ctx context.Context, id string) (model.User, error)
        +FindAll(ctx context.Context) ([]model.User, error)
    }

    `service.UserSession` --> `UserSessionRepository`

    class `UserSessionRepository` {
        <<Interface>>
        +Create(ctx context.Context, us model.UserSession) error
	    +FindByID(ctx context.Context, id string) (model.UserSession, error)
    }

    class `QuoteRepository` {
        <<Interface>>
        +Create(ctx context.Context, q model.Quote) error
        +Update(ctx context.Context, q model.Quote) error
        +FindByID(ctx context.Context, id string) (model.Quote, error)
        +FindAll(ctx context.Context) ([]model.Quote, error)
    }

    class `service.Quote` {
        -repo QuoteRepository
        +CreateQuote(ctx context.Context, q *model.Quote) error
        +GetAllQuotes(ctx context.Context) ([]model.Quote, error)
    }

    `server` --> `service.Quote`
    `service.Quote` --> `QuoteRepository`

    class `service.OIDC` {
        +Name string
        +IssuerURL string
        +ClientID     string
        +ClientSecret string
        -config   oauth2.Config
        -provider *oidc.Provider
        +RedirectURL(state string, nonce string) (url string)
        +CallbackURL() string
        +Init(baseURL string) error
        +ValidateCallback(r http.Request) (oidc.IDToken, error)
    }

    `server` --> `service.OIDC`
Loading