Code Monkey home page Code Monkey logo

model's Introduction

Model

Model

Release Go Doc GitHub Action: Test Go Report Card Software License
Slack: Goa Slack: Sign-up Twitter: @goadesign

Overview

Model provides a way to describe software architecture models using diagrams as code. This approach provides many benefit over the use of graphical tools, in particular:

  • Built-in history via source control versioning: Each change to the software architecture model results in a commit making it natural to implement Architectural Decision Records.
  • Simple and consistent reuse of shared architecture components: The code of shared architecture components can be imported by other software systems. Apart from the obvious time saving a major advantage is ensuring that all references are automatically kept up-to-date as the shared architecture component evolves. The shared architecture component code can be versioned using traditional source code versioning techniques as well.
  • Consistent notation and visual styles: The same mechanism used to import shared architecture diagram can also be used to import shared style definitions.
  • Ability to control drift between reality and diagrams: Static code analysis allows writing tools that compare the software models with actual code to detect discrepencies (this repo does not provide such a tool at this time).

The Model DSL is implemented in Go and follows the C4 Model to describe the software architecture. Using Go to implement the DSL makes it possible to leverage Go packages to share and version models. It also allows for extending or customizing the DSL by writing simple Go functions.

The C4 (Context, Containers, Components and Code) model provides a clean and simple set of constructs (software elements, deployment nodes and views) that can be learned in minutes. The C4 model is very flexible and only focuses on a few key concepts making it possible to express many different styles of architectures while still adding value.

Example

Here is a complete design describing a simple architecture model composed of a user and a software system:

package design

import . "goa.design/model/dsl"

var _ = Design("Getting Started", "This is a model of my software system.", func() {
    var System = SoftwareSystem("Software System", "My software system.", func() {
        Tag("system")
    })

    Person("User", "A user of my software system.", func() {
        Uses(System, "Uses")
        Tag("person")
    })

    Views(func() {
        SystemContextView(System, "SystemContext", "An example of a System Context diagram.", func() {
            AddAll()
            AutoLayout(RankLeftRight)
        })
        Styles(func() {
            ElementStyle("system", func() {
                Background("#1168bd")
                Color("#ffffff")
            })
            ElementStyle("person", func() {
                Shape(ShapePerson)
                Background("#08427b")
                Color("#ffffff")
            })
        })
    })
})

This code creates a model containing two elements, a relationship, a single view and some styling information. Running the mdl tool (see installation instructions below) renders the following diagram:

Basic example

Additional examples can be found in the examples directory.

Installation

Model uses Go modules and thus requires Go version 1.11 or greater. Assuming a working installation of Go, the mdl and stz tools can be installed using:

go install goa.design/model/cmd/mdl
go install goa.design/model/cmd/stz

Usage

Model includes two command line tools supporting two different workflows:

  • The mdl tool serves a graphical editor that makes it possible to position the elements and relationships in each view defined in the design. The graphical editor saves the rendered views as SVG files in a directory specified when running the tool. mdl can also generate a JSON representation of the model.

  • The stz tool uploads the software architecture described in the DSL to the Structurizr service. This service renders the model and includes a visual editor to rearrange the results (the tool takes care of keeping any change made graphically on the next upload).

Model flows

Diagram source code: model.go

Model also provides a Goa plugin so that the design of APIs and microservices written in Goa can be augmented with a description of the corresponding software architecture.

Using mdl

The mdl serve command starts a local HTTP server that serves a graphical editor. The command takes the Go import path to the package containing the DSL as argument. The path to the directory used to save the SVG files can be provided via the -dir flag, by default the editor creates a gen folder under the current path. For example:

mdl serve goa.design/model/examples/basic/model -dir gen
Watching: /home/raphael/go/src/goa.design/model/examples/basic/model
mdl v1.9.7, editor started. Open http://localhost:8080 in your browser.

Modifying and saving the DSL while the editor is running causes it to automatically update and reflect the latest changes making it convenient to work on the model while editing the view layouts.

The mdl gen command generates the JSON representation of a design, it accepts the Go import path to the package containing the design DSL as argument. For example the following command generates the JSON for the basic example

mdl gen goa.design/model/examples/basic/model -out design.json

The generated file design.json contains a JSON representation of the Design struct.

Using stz

Alternatively, the stz tool generates a file containing a JSON representation of a Structurize service workspace that corresponds to the design described in the DSL. The tool can then upload the workspace to the Structurizr service. The tool can also retrieve the JSON representation of a workspace from the service.

This example uploads a workspace corresponding to the DSL defined in the Go package goa.design/model/examples/basic:

stz gen goa.design/model/examples/basic/model
stz put workspace.json -id ID -key KEY -secret SECRET

In this example ID is the Structurizr service workspace ID, KEY the Structurizr service API key and SECRET the corresponding secret.

The example below retrieves the JSON representation of a workspace from Structurizr:

stz get -id ID -key KEY -secret SECRET -out workspace.json

Using the Goa Plugin

This package can also be used as a Goa plugin by including the DSL package in the Goa design:

package design

import . "goa.design/goa/v3/dsl"
import "goa.design/model/dsl"

// ... DSL describing API, services and architecture model

Running goa gen creates both a design.json and a workspace.json file in the gen folder. The workspace.json file follows the structurizr JSON schema and can be uploaded to the Structurizr service for example using the stz tool included in this repo.

Using Model as a library

The mdl package RunDSL method runs the DSL and produces data structures that contain all the information needed to render the views it defines.

The stz package RunDSL method runs the DSL and produces a data structure that can be serialized into JSON and uploaded to the Structurizr service.

The stz package also contains a client library for the Structurizr service APIs.

Here is a complete example that uploads the design described in a DSL to the Structurizr service:

package main

import (
    "fmt"
    "os"

    . "goa.design/model/dsl"
    "goa.design/model/stz"
)

// DSL that describes software architecture model.
var _ = Design("Getting Started", "This is a model of my software system.", func() {
    var System = SoftwareSystem("Software System", "My software system.", func() {
        Tag("system")
    })

    Person("User", "A user of my software system.", func() {
        Uses(System, "Uses")
        Tag("person")
    })

    Views(func() {
        SystemContextView(System, "SystemContext", "An example of a System Context diagram.", func() {
            AddAll()
            AutoLayout(RankLeftRight)
        })
        Styles(func() {
            ElementStyle("system", func() {
                Background("#1168bd")
                Color("#ffffff")
            })
            ElementStyle("person", func() {
                Shape(ShapePerson)
                Background("#08427b")
                Color("#ffffff")
            })
        })
    })
})

// Executes the DSL and uploads the corresponding workspace to Structurizr.
func main() {
    // Run the model DSL
    w, err := stz.RunDSL()
    if err != nil {
        fmt.Fprintf(os.Stderr, "invalid design: %s", err.Error())
        os.Exit(1)
    }

    // Upload the design to the Structurizr service.
    // The API key and secret must be set in the STRUCTURIZR_KEY and
    // STRUCTURIZR_SECRET environment variables respectively. The
    // workspace ID must be set in STRUCTURIZR_WORKSPACE_ID.
    var (
        key    = os.Getenv("STRUCTURIZR_KEY")
        secret = os.Getenv("STRUCTURIZR_SECRET")
        wid    = os.Getenv("STRUCTURIZR_WORKSPACE_ID")
    )
    if key == "" || secret == "" || wid == "" {
        fmt.Fprintln(os.Stderr, "missing STRUCTURIZR_KEY, STRUCTURIZR_SECRET or STRUCTURIZR_WORKSPACE_ID environment variable.")
        os.Exit(1)
    }
    c := stz.NewClient(key, secret)
    if err := c.Put(wid, w); err != nil {
        fmt.Fprintf(os.Stderr, "failed to store workspace: %s\n", err.Error())
        os.Exit(1)
    }
}

DSL Syntax

Rules

The following rules apply to all elements and views declared in a model:

  • Software and people names must be unique.
  • Container names must be unique within the context of a software system.
  • Component names must be unique within the context of a container.
  • Deployment node names must be unique with their parent context.
  • Infrastructure node names must be unique with their parent context.
  • All relationships from a given source element to a given destination element must have a unique description.
  • View keys must be unique.

Note that uniqueness of names is enforced by combining the evaluated definitions for a given element. For example if a model contained:

var Person1 = Person("User", "A user", func() {
    Uses("System 1", "Uses")
})
var Person2 = Person("User", "The same user again", func() {
    Uses("System 2", "Uses")
})

Then the final model would only define a single person named "User" with the description "The same user again" and both relationships. This makes it possible to import shared models and "edit" existing elements, for example to add new relationships.

References to Elements

Some DSL functions such as Uses, Delivers, InteractsWith, Add and Link accept references to elements as argument. The references can be done either through a variable (which holds the element being referred to) or by the path of the element. The path of an element is constructured by appending the parent element names separated by slashes (/) and the name of the element. For example the path of the component 'C' in the container 'CO' and software system 'S' is 'S/CO/C'. The path can be relative when the reference is made within a scoped function, for example when adding an element to a view that is scoped to a parent element.

Resources

The DSL package documentation lists all the DSL keywords and their usage.

The file DSL.md illustrates the complete syntax in one design.

Graphical Editor

The graphical editor is started using the mdl serve command. This command accepts the import path to the Go package containing the model DSL. The editor makes it possible to position the elements and their relationships in each view.

The editor makes it possible to select a view described in the DSL and to position its elements and relationships graphically. Multiple elements can be selected by either dragging over the selection or by using SHIFT + CLICK. Selecting multiple elements automatically selects the relationships between these elements as well.

New vertices can be created on relationship lines using ALT + CLICK, existing vertices can be deleted with BACKSPACE or DELETE. See the table below for a complete list of editor shortcuts.

Saving

The Save View button causes the editor to create a SVG rendering of the currently edited view in the target directory (specified via the -dir flag). The SVG encodes the positions of all the elements and relationships as well so that restarting the editor using the same target directory automatically restores the element positions.

Editor

Diagram source code: model.go

Keyboard Shortcuts

The editor supports a number of keyboard shortcuts listed below:

Category Shortcut Effect
Help ?, SHIFT + F1 Show keyboard shortcuts
File CTRL + S Save SVG
History CTRL + Z Undo
History CTRL + SHIFT + Z, CTRL + Y Redo
Relationship editing ALT + CLICK Add relationship vertex
Relationship editing ALT + SHIFT + CLICK Add label anchor relationship vertex
Relationship editing DELETE, BACKSPACE Remove relationship vertex
Zoom CTRL + =, CTRL + wheel Zoom in
Zoom CTRL + -, CTRL + wheel Zoom out
Zoom CTRL + 9 Zoom - fit
Zoom CTRL + 0 Zoom 100%
Select CTRL + A Select all
Select ESC Deselect
Move UP Move up
Move SHIFT + UP Move up fast
Move DOWN Move down
Move SHIFT + DOWN Move down fast
Move RIGHT Move right
Move SHIFT + RIGHT Move right fast
Move LEFT Move left
Move SHIFT + LEFT Move left fast

TBD

While functional, the editor isn't considered feature complete yet. Here is the list of features that will be added in the future:

  • Snap to grid
  • Better SVG clipping on save
  • Autolayout on start if no pre-existing layout information
  • Support for both vertical and horizontal ranking with autolayout
  • Keyboard shortcuts for vertical and horizontal align
  • Autosave toggle
  • Distribute horizontally and vertically
  • Toolbar icons

Examples

Refer to the examples directory for working examples.

model's People

Contributors

arangamani avatar beffge avatar dependabot[bot] avatar dvictor avatar puneetpunamiya avatar raphael avatar tchssk avatar vernonr3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

model's Issues

ElementHolder

This parameter type ElementHolder allows almost anything to be passed in (for example the code in views.go doesn't check for the existence of elements within the structs that are inspected in subsidiary functions.
This lead to unexpected crashes as tests (e.g. views_test.go) were developed. More seriously it could lead to unexpected crashes/bugs if more code uses these functions. More defensive coding might be worth adopting.

Example usage does not work as expected

Hi, I am trying this program out and I am struggling with the installation according to the instruction.

The following commands

go install goa.design/model/cmd/mdl
go install goa.design/model/cmd/stz

were a bit tricky to figure out as I wasn't sure where the bins are installed (I am not that experienced with Go) but I eventually found them in ~/go/bin.

However, I am struggling with this one
mdl serve goa.design/model/examples/basic/model -dir gen
I switched to ~/go/bin and ran

mdl serve goa.design/model/examples/basic/model -dir gen

but I get the following error
go: go.mod file not found in current directory or any parent directory; see 'go help modules'

Is there anything missing in the instructions, perhaps? I am running this in WSL2 - Ubuntu - 22.04.

Import DSL/JSON file

Context

Hi!

I started using Structurizr and really like the Structurizr DSL. I started building some diagrams, and would like to supplement them with some code. Since I mainly use Go, this repo looks like a good fit.

Problem

While we can export code diagrams to SVG and/or workspaces, I didn't find a way of doing it the other way around: pulling workspaces, or reading .dsl/.json files.

I would like to do two things:

  • read existing diagrams -> generate Go code from them
  • read existing diagrams -> generate SVG diagrams from them

Question

Is there a way of doing that with your repo?
If not, where should I look at to get started and start working on it?

Enhanced directory watch in mdl serve

A pattern that we're using for sharing the design objects between different repositories without also sharing the views is to use the following pattern:

  • diagram/model.go contains all of the design object definitions
  • diagram/views/views.go contains the views specific to the repository
  • Run mdl serve -dir diagram/views to build and edit diagrams from the views

However, this means that the file system watch for changes that the current implementation of mdl serve sets up only looks at diagram/views and any subdirectories, so design object changes are not noticed and neither the livereload or a page refresh of the editor will bring in those changes.

One solution would be to invert the pattern so that the views live in the shallower directory while the design objects move down to a deeper directory:

  • diagram/views.go
  • diagram/model/model.go
  • Run mdl serve -dir diagram

But, I'm also wondering if we might want to allow for more flexibility by either allowing the specification of extra directories to watch or looking at the package imports and watching the directories that are found from them that are also in the same Go module.

Filtered Views Example Broken

I'm trying to figure out how to make a FilteredView, but I'm not understanding how it's done. I tried to run the example code provided for FilteredView from here but it errors out.

First, it complains that AutoLayout has the wrong number of arguments, so I set that to RankLeftRight to fix that issue. Next, it says invalid use of FilteredView in views and that's where I'm stuck in my own code. I'm not sure where to get the expr.View that it's expecting. The example uses SystemContextView, but that can't be right, it's a function so it can't implement the expr.View interface.

It would be nice if the mdl serve livereload port were specifiable or the same as the -port port

I noticed while trying to run a second instance of mdl serve with a different port number, that it still failed due to trying to bind to port 35728:

listen tcp :35729: bind: address already in use

It seems like the code is currently always passing the default port from the github.com/jaschaephraim/lrserver package when starting it, so it is definitely possible to pass a different value as a parameter.

It would probably be a little more work to get a live reload handler into the main mdl serve server since the github.com/jaschaephraim/lrserver package doesn't currently provide an exported handler method that could be used, but that would mean that you only need to pass one parameter in order to use multiple instances of mdl serve concurrently.

module goa.design/plugins@latest found (v2.1.3+incompatible), but does not contain package goa.design/plugins/structurizr/dsl

Hi,

I'm running the example from the readme but seemingly i have a dependency problem with the DSL

(base) [marvin@marvin-pc ArchAsCode]$ go run arch_as_code.go go: finding module for package goa.design/plugins/structurizr/dsl go: finding module for package goa.design/goa/v3/dsl go: downloading goa.design/goa v1.4.3 go: downloading goa.design/goa/v3 v3.1.3 go: downloading goa.design/plugins v2.1.3+incompatible go: downloading goa.design/goa v2.1.3+incompatible go: found goa.design/goa/v3/dsl in goa.design/goa/v3 v3.1.3 go: finding module for package goa.design/plugins/structurizr/dsl go: downloading github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d go: downloading github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 go: downloading github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea trth_db_loader.go:4:8: module goa.design/plugins@latest found (v2.1.3+incompatible), but does not contain package goa.design/plugins/structurizr/dsl

go version go1.14.4 Linux/amd64

Sorry I'm relatively new to Go let me know if any more information is necessary/helpful

Problem unit testing

I've been trying to add unit tests giving fairly complete coverage to /expr/views.go. I've hit a problem with f
func (dv *DeploymentView) AddElements(ehs ...ElementHolder) error {

It may be my lack of understanding of the way the software is supposed to work...

func (dv *DeploymentView) AddElements(ehs ...ElementHolder) error {
	var nodes []*DeploymentNode
	for _, eh := range ehs {
		switch e := eh.(type) {
		case *DeploymentNode:
			if addDeploymentNodeChildren(dv, e) {
				nodes = append(nodes, e)
			}
		case *ContainerInstance:
			if dv.SoftwareSystemID == "" || dv.SoftwareSystemID == Registry[e.ContainerID].(*Container).System.ID {
				addElements(dv.ViewProps, e)
				nodes = append(nodes, e.Parent)
			}
		case *InfrastructureNode:
			addElements(dv.ViewProps, e)
			nodes = append(nodes, e.Parent)
		default:
			return fmt.Errorf("elements of type %T cannot be added to deployment views", eh)
		}
	}
.,.
}

The problem I'm seeing is in the case *ContainerInstance - line 440. The test asks whether dv.SoftwareSystemID == Registry[e.ContainerID] etc.
A good unit test would load Registry map with the e.ContainerID and then execute the AddElements function passing it the appropriate container.
However, the Registry map is created in code in expr/registry.go at line 54. This sets the key to idify(e.System.ID + ":" + e.Name). Since idify appears (if I understand it correctly) to use hashing ( a one way function) - and the function implies there will be a minimum of 2 keys in the map - I'm wondering how to work out which key refers to the container, which to the system etc.
I can understand why one wants unique keys - but why use a one way function? If a two way transform were used it would be much easier to work out the identity of the item in the registry?
Have I missed the point / completely misunderstood?

Properly render external systems in container views

Could be related to #648

When creating a container view that contains references to external systems mdl does not properly render the external system boundary. Instead it creates separate boxes for the system and its containers.

Example DSL:

package model

import . "goa.design/model/dsl"

var _ = Design("Example", "An example with a container view containing two systems", func() {
	SoftwareSystem("SystemA", "System A", func() {
		Container("ContainerA", "Container A", "Does something", func() {
			Uses("SystemB/ContainerB", "Uses")
		})
	})

	SoftwareSystem("SystemB", "System B", func() {
		Container("ContainerB", "Container B", "Does something", func() {
			Uses("ContainerC", "Uses")
		})
		Container("ContainerC", "Container C", "Does something", func() {
		})
	})

	Views(func() {
		ContainerView("SystemA", "Container view", func() {
			AddAll()
			Add("SystemB/ContainerB")
			Add("SystemB/ContainerC")
		})
	})
})

Rendered diagram:

Screenshot 2024-04-29 at 1 30 39โ€ฏPM

As comparison here is a similar situation in structurizr DSL:

workspace {
    model {
        user = person "User"
        softwareSystem = softwareSystem "Software System" {
            webapp = container "Web Application" {
                user -> this "Uses"
            }
            container "Database" {
                webapp -> this "Reads from and writes to"
            }
        }
        softwareSystem2 = softwareSystem "Software System 2" {
            webapp2 = container "Web Application 2" {
                user -> this "Uses"
            }
            webapp3 = container "Web Application 3" {
                webapp2 -> this "Uses"
            }
            container "Database 2" {
                webapp2 -> this "Reads from and writes to"
            }
        }
    }
    views {
        container softwareSystem {
            include *
            include webapp2
            include webapp3
            autolayout lr
        }
        theme default
    }
}

And the diagram:

Screenshot 2024-04-29 at 1 32 10โ€ฏPM

cc @dvictor

examples do not work as expected

steps to reproduce:

  • clone the repo
  • cd examples/basic
  • go run main.go

expected result:
command would complete without any error

actual result:
invalid design: [model/model.go:11] missing arguments in person "User"exit status 1

Links between top-level deployment nodes in DeploymentView

Hi folks!

After a few hours of debugging to figure out why the links between my containers were not being shown in my deployment view, I saw

model/expr/render.go

Lines 101 to 106 in 2461d2e

// Do not automatically add relationship views across different
// top-level deployment nodes.
//
// Note: this rule is a little bit arbitrary however it is possible
// to override the behavior using `Link` and `Unlink` explicitly in
// the design. We'll see how that works out over time.

While it is true that you can technically add them using Link, it's not a great DX. Would you be interested on a PR adding something like AddTopLevelLinks (similar to the current AddAll and AddDefault) that is only applicable inside a DeploymentView?

[Question] Person vs Actor disambiguation

I started using goadesign/model recently, thank you for your work on this project.

The model I'm working on does not contain a "Person", as in, there is no human being involved. The reason for this is because it is a subsystem of the wider organisation architecture, that only deals with other subsystems, not people.
A limitation I found on this project, is that I always need to define a "Person" otherwise mdl does not render the view on the browser. I understand that even a subsystem needs actors, as in, an entity that interacts with the subsystem.
To me, it begs the question, why do we call it Person, instead of Actor ? Using the word Person is assuming a human being is interacting with your system, which is not always the case.

Not opening in Structurizr Lite

Just tried to generate a few workspace.json either from examples for my own code, but none of that opens in the current version of Structurizr Lite.

My first attempt produces the following error in the Structurizr UI:

Error
Cannot invoke "com.structurizr.documentation.Documentation.getSections()" because the return value of "com.structurizr.Workspace.getDocumentation()" is null

Apparently, Structurizr expects documentation section to be present. I could work around that problem by using RunDSL and manually adding documentation afterwards.

At that point, I get the following error from Strcturizr (in the log):

Warning: syntax ambiguity - badly delimited number '18u' in line 7 of /usr/local/structurizr/.structurizr/graphviz/1/SystemContext.dot splits into two tokens
Warning: syntax ambiguity - badly delimited number '18u' in line 7 of /usr/local/structurizr/.structurizr/graphviz/1/SystemContext.dot splits into two tokens
Error: /usr/local/structurizr/.structurizr/graphviz/1/SystemContext.dot: syntax error in line 7 near ','
java.io.FileNotFoundException: /usr/local/structurizr/.structurizr/graphviz/1/SystemContext.dot.svg (No such file or directory)

Line 7 is the id field of the first software system. I believe that Structurizr (or graphviz) has some issues with the format of the IDs. As it turns out, I tried to implement something like this component yesterday myself - I initially used guids for the IDs and that didn't work either. However, regular numbers did.

I am running Structurizr Lite build 3047 via the official docker image.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.