Git Product home page Git Product logo

mbryzek / schema-evolution-manager Goto Github PK

View Code? Open in Web Editor NEW
267.0 66.0 47.0 304 KB

Schema Evolution Manager makes it very simple for engineers to contribute schema changes to a postgresql database, managing the schema evolutions as proper source code. Schema changes are deployed as gzipped tarballs named with the corresponding git tag.

License: Apache License 2.0

Ruby 92.24% PLpgSQL 6.45% Shell 1.31%

schema-evolution-manager's Introduction

Build Status

Schema Evolution Manager (sem)

Intended Audience

  • Engineers who regularly manage the creation of scripts to update the schema in a postgresql database.

  • Engineers who want to simplify and/or standardize how other team members contribute schema changes to a postgresql database.

Purpose

Schema Evolution Manager (sem) makes it very simple for engineers to contribute schema changes to a postgresql database, managing the schema evolutions as proper source code. Schema changes are deployed as gzipped tarballs named with the corresponding git tag.

To apply schema changes to a particular database, download a tarball and use sem to figure out which scripts have not yet been applied, then apply those scripts in chronological order.

sem provides well tested, simple tools to manage the process of creating and applying schema upgrade scripts to databases in all environments.

  • scripts are automatically named with a timestamp assigned at time of creation

  • all scripts applied to the postgresql database are recorded in the table schema_evolution_manager.scripts - making it simple to see what has been applied, and when, if needed.

sem contains only tools for managing schema evolutions. The idea is that you create one git repository for each of your databases then use sem to manage the schema evolution of each database.

At Gilt Groupe, we started using sem in early 2012 and have observed an increase in the reliability of our production schema deploys across dozens of independent postgresql databases.

See INSTALLATION and GETTING STARTED for details.

Project Goals

  • Absolutely minimal set of dependencies. We found that anything more complex led developers to prefer to manage their own schema evolutions. We prefer small sets of scripts that each do one thing well.

  • Committed to true simplicity - features that would add complexity are not added. We hope that more advanced features might be built on top of schema evolution manager.

  • Works for ALL applications - schema management is a first class task now so any application framework can leverage these migration tools.

  • No rollback. We have found in practice that rolling back schema changes is not 100% reliable. Therefore we inentionally do NOT support rollback. This is an often debated element of sem, and although the design itself could be easily extended to support rollback, we currently have no plans to do so.

In place of rollback, we prefer to keep focus on the criticalness of schema changes, encouraging peer review and lots of smaller evolutions that themselves are relatively harmless.

This stems from the idea that we believe schema evolutions are fundamentally risky. We believe the best way to manage this risk is to:

  1. Treat schema evolution changes as normal software releases as much as possible

  2. Manage schema versions as simple tarballs - artifacts are critical to provide 100% reproducibility. This means the exact same artifacts can be applied in development then QA and finally production environments.

  3. Isolate schema changes as their own deploy. This then guarantees that every other application itself can be rolled back if needed. In practice, we have seen greater risk when applications couple code changes with schema changes.

This last point bears some more detail. By fundamentally deciding to manage and release schema changes independent of application changes:

  1. Schema changes are required to be incremental. For example, to rename a column takes 4 separate, independent production deploys:
a. add new column
b. deploy changes in application to use old and new column
c. remove old column
d. deploy changes in application to use only new column

Though at first this may seem more complex, each individual change itself is smaller and lower risk.

  1. It is worth repeating that all application deploys can now be rolled back. This has been a huge win for our teams.

Talks

First presented at PGDay NYC 2013

Dependencies

  • Ruby: Current testing against ruby 2.x. 1.8 and 1.9 are supported.

  • Postgres: Only tested against 9.x. We minimize use of advanced features and should work against 8.x series. If you try 8.x and run into problems, please let us know so we can update.

  • plpgsql must be available in the database. If needed you can:

    createlang plpgsql template1

  • Git: Designed to use git for history (all versions since 1.7).

Installation

There are three ways to install schema evolution manager:

  1. Via brew

     brew install schema-evolution-manager
    
  2. Direct install using ruby (no dependency on ruby gems)

     git clone [email protected]:mbryzek/schema-evolution-manager.git
     cd schema-evolution-manager
     git checkout 0.9.54
     ruby ./configure.rb
     sudo ./install.rb
    
  3. If you have ruby gems:

     gem install schema-evolution-manager
    

Upgrading

Upgrading is as simple as following the Installation instructions for the new version. Each installation of sem will create a new directory for that specific version. When you install the newer version, a new directory will be created and symlinks updated to point to the latest version.

Getting Started

Initialization

git init /tmp/sample
sem-init --dir /tmp/sample --url postgresql://postgres@localhost/sample

Writing your first sql script

cd /tmp/sample
echo "create table tmp_table (id integer)" > new.sql
sem-add ./new.sql

Applying changes to your local database:

cd /tmp/sample
createdb sample
sem-apply --url postgresql://postgres@localhost/sample

Note that you can also pass in the username, db host, and db name explicitly:

sem-apply --host localhost --name sample --user postgres

Similarly, for non-standard setups, you can optionally pass in the port

sem-apply --host localhost --port 5433 --name sample --user postgres

When you are happy with your change, commit:

git commit -m "Adding a new tmp table to test sem process" scripts

Publishing a Release

cd /tmp/sample
sem-dist

By default, the sem-dist script will create the next micro git tag, and use that tag in the file name.

If you already have a tag:

sem-dist --tag 0.0.2

You will now have a single artifict - /tmp/sample/dist/sample-0.0.2.tar.gz - that you can manage in standard deploy process.

Deploying Schema Changes

Extract tarball on server

scp /tmp/sample/dist/sample-0.0.2.tar.gz <your server>:~/
ssh <your server>
tar xfz sample-0.0.2.tar.gz
cd sample-0.0.2

Do a dry run

sem-apply --url postgresql://postgres@localhost/sample --dry_run

You will likely see a number of create table statements (see data model section below). You should also see:

  [DRY RUN] Applying 20130318-214407.sql

which tells you that if you apply these changes, that sql script will be applied to the sample db

Specifying database password

There are two recommended ways in which to pass user passwords to psql:

  1. Create a ~/.pgpass file with the appropriate credentials

  2. Specify a [--password] flag when running sem-apply. You will then be prompted to enter your password once. sem will create a temporary file to store your password, using that file during the duration of the command and ensuring the file is deleted after sem completed.

    Example:

    sem-apply --url postgresql://postgres@localhost/sample --password
    

Apply the changes

sem-apply --url postgresql://postgres@localhost/sample

You will see:

  Upgrading schema for postgres@localhost/sample
  Applying 20130318-214407.sql

Attempt to apply again:

sem-apply --url postgresql://postgres@localhost/sample

You will see:

  Upgrading schema for postgres@localhost/sample
    All scripts have been previously applied

Baselines

If you have an existing database, and you want to start using schema evolution manager, we support the notion of creating a baseline. The sem-baseline script will record that all of the scripts have been applied to the database, without actually applying them. From this point forward, only new scripts will be applied to the database.

sem-baseline --url postgresql://postgres@localhost/sample

Data Model

sem will create a new postgresql schema in your database named 'schema_evolution_manager'

psql sample
set search_path to schema_evolution_manager;
\dt

    Schema    |       Name        | Type  |  Owner
 -------------+-------------------+-------+----------
 schema_evolution_manager | bootstrap_scripts | table | postgres
 schema_evolution_manager | scripts           | table | postgres

Each of these tables has a column named 'filename' which keeps track of the sql files applied to each database.

  • The scripts table is used for your application.
  • The bootstrap_scripts table is used to manage upgrades to the sem application itself.

For details on these tables, see scripts/*sql where the tables themselves are defined.

PLPGSQL Utilities

We've included a copy of the schema conventions we practice at Gilt Groupe. There are also a number of utility plpgsql functions to help developers apply these conventions in a systematic way.

The helpers are defined in

scripts/20130318-105456.sql

We have found these utilities incredibly useful - and are committed to providing only the most relevant, high quality, and extremely clear helpers as possible.

In CONVENTIONS.md you will find a simple example of these conventions and utilities in practice.

Command Line Utilities

  • sem-init: Initialize a git repository for sem support
  • sem-add: Adds a database upgrade script
  • sem-dist: Create a distribution tar.gz file containing schema upgrade scripts
  • sem-apply: Apply any deltas from a distribution tarball to a particular database
  • sem-baseline: Add any migration scripts to the schema tables without actually applying them. See Migrating

Attributes supported in sql migration scripts

Sometimes you may want to adjust the specific options used by SEM when applying SQL scripts. Attributes can be specified within each SQL file in comments.

To specify an attribute, add a comment of the following format anywhere in your SQL file (but at the top by convention):

-- sem.attribute.[name] = [value]

Currently supported attributes:

  • transaction

    • single (default): the entire file is applied within a transaction (by using the psql command line argument --single-transaction)
    • none: Each command in the file will be applied in order. If a later command in the file fails, there will be no rollback.

    Examples:

    • -- sem.attribute.transaction = none
    • -- sem.attribute.transaction = single

Migrating

In some cases you may be migrating from no schema evolutions, or another schema evolution model.

For these cases, sem provides a 'baseline' command.

Current workflow:

  1. sem-add your current schema 1. Either via a database dump 1. Or by sem-adding existing DB scripts
  2. Use sem-baseline to bootstrap the sem tables and add existing schema files to sem's migration table without actually applying them

License

Copyright 2013-2016 Gilt Groupe, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

schema-evolution-manager's People

Contributors

amanfredi avatar azrosen92 avatar benwaffle avatar chrhicks avatar dawidd6 avatar gheine avatar jlogeart avatar kscaldef avatar matija avatar mbryzek avatar michae1t avatar rlmartin 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  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

schema-evolution-manager's Issues

calling sem-add in quick succession overwrites updates

I'm porting old sql schema changes to sem. I scripted the process and called sem-add on each change.

sem-add ./update-002.sql ; git commit -m "db schema update 002"
sem-add ./update-003.sql ; git commit -m "db schema update 003"
sem-add ./update-004.sql ; git commit -m "db schema update 004"
.
.
sem-add ./update-364.sql ; git commit -m "db schema update 364"
sem-add ./update-365.sql ; git commit -m "db schema update 365"

I got files in the scripts directory:

20150423-205830.sql
20150423-205831.sql
20150423-205832.sql
20150423-205833.sql
20150423-205834.sql
20150423-205835.sql
20150423-205836.sql
20150423-205837.sql
20150423-205838.sql
20150423-205839.sql
20150423-205840.sql
20150423-205841.sql
20150423-205842.sql
20150423-205843.sql
20150423-205844.sql
20150423-205845.sql
20150423-205846.sql
20150423-205847.sql
20150423-205848.sql

19 files, versus 365 files added using sem-add. Inspecting the files, I see that there are gaps in my 1-365 sequence.

Using time-stamps is too a naïve method in this case. I will work around by sleeping for a second between executing sem-add.

ps. Thanks for this tool!

comparison with other tools

It would be nice if the readme discussed why you might or might not want to use schema-evolution-manager in comparison with alternatives such as sqitch and flyway.

Change on behavior - sem-apply not applying the migrations if run in non interactive scripts

Hi there,

First of all, thanks a lot for this repo. It's my favorite choice as a simple tool for applying migrations in all my projects.

Last week I noticed a change on behavior. I'm running always the "sem apply" in my clients docker to run all the migrations, and then I start the services (e.g. backend, etc). It always run OK since last week it stopped applying this migrations, because it prompts the following message to the users:

Please confirm that you would like to apply all (<n>) of the pending scripts:
...

As it's running inside a docker an non interactive way, it ignores the migrations and continue with the script.

I think it's related to #76 . It would be better if the default value of the non_interactive parameter were 'true' so that the behavior remains the same before and after the change.. If you want, I can do a PR for this.

Thanks,

Create a sem-baseline command

Ran into this at my current company, where someone went to prod without sem and now needs to switch.

Need the ability to apply the bootstrap scripts and add an initial migration file to the migration table without actually applying it (since it has already been applied).

Baseline is terminology taken from other schema migration tools.

Remove need for tmp file when applying scripts

another idea would be to remove the temp file all together. This one threw me away initially as I had issues in the sql and the line report didn't make any sense to me, until I read the code (and also doublechecked nothing else was added, by hacking the code to not delete the file and inspect the tmp afterward).

So the psql command can accept the flag via arguments with --set ON_ERROR_STOP=on which would avoid the need to copy/modify the file and just use the source directly.

Support custom ports

I'm running a postgres docker container, and as not to interfere with my existing postgres setup, I am exposing it on a non-standard port. Should be able to specify an optional port to sem-apply in order to support this, and other non-standard setups.

--password flag doesn't work

[ec2-user@ip-10-20-2-47 harmonization-postgresql]$ sem-apply --user api --host xxx --name harmonizationdb --password
Please enter the database user password[xxxxx]
/usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/library.rb:106:in block in write_to_temp_file': no block given (yield) (LocalJumpError) from /usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/library.rb:88:inwith_temp_file'
from /usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/library.rb:102:in write_to_temp_file' from /usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/db.rb:14:ininitialize'
from /usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/db.rb:105:in new' from /usr/local/lib/schema-evolution-manager-0.9.26/lib/schema-evolution-manager/db.rb:105:infrom_args'
from /usr/local/bin/sem-apply:26:in `

'

sem-apply --set parameter cannot be repeated

I tried multiple psql parameters in a script:

create role user_ro login password :'ro_pwd';
create role user_rw login password :'rw_pwd';

When I run sem-apply with multiple --set:

sem-apply --url xxx --set ro_pwd=abc --set rw_pwd=def

I get script errors. Running the script directly with psql does not have those errors. On investigation, only one of the variables gets passed to psql. (The last variable wins.)

Option to specify that a script runs outside the scope of a transaction

I was wondering what are your thoughts on something.

There are cases when it is not possible to execute a alter within a transaction, like when we want to do alter type.

http://www.postgresql.org/docs/9.1/static/sql-altertype.html

So we need to execute this change outside of a transaction.

psql is invoked with --single-transaction.

I was thinking a half decent way could be to require a specific tag in a comment as the first line of the sql file like:

-- @transaction:off

or something like that.

What do you think ?

Another solution is to break compatibility and require every file to have BEGIN/COMMIT in its sql if it really needs to be in a transaction, but given this is what is needed in 99.9999% of the cases, this is also nonoptimal

Any other idea ?

Underlying errors are suppressed

Hi there,

I'm trying to troubleshoot an issue in one of our scripts. For some reason, SEM does not print the details of the underlying Postgres problem when applying the change, like you can see in this log:

Upgrading schema for postgres://schemaManagerRole@schema-system-test.ckanakw8vqgo.eu-west-1.rds.amazonaws.com:5432/afoom
Applying 20160424-112521.sql

ERROR applying script: 20160424-112521.sql

If this script has previously been applied to this database, you can record it as having been applied by:
 psql --command "insert into schema_evolution_manager.scripts (filename) values ('20160424-112521.sql')" postgres://schemaManagerRole@schema-system-test.ckanakw8vqgo.eu-west-1.rds.amazonaws.com:5432/afoom

Is there anything you can suggest that will help me get the details out so we can figure out what is wrong?

Package as a gem

Much easier to install across various environments if we are able to just type

gem install schema-evolution-manager

rather than checking out the repo and following the instructions in the README.

Invalid command line on windows systems

Executing the Db scripts from Windows leads to "Invalid Command Line" due to stderr redirect notation that is not compatible. ( &> )
I'd suggest to to update the line 77 of db.rb with:

 result = `#{command} > #{output} 2>&1`.strip

instead of

 result = `#{command} &> #{output}`.strip

Extra semicolon in latest version

Hi -
This commit that created a new version just now seems to have an extra semicolon here

new.updated_at = now();;

This is giving a syntax error at least on PostgreSQL 13 that we are using

ERROR:  syntax error at or near ";"
LINE 6:     new.updated_at = now();;
                                   ^

Thanks

New dependency on io/console unavailable

[ec2-user@ip-10-20-2-47 schema-evolution-manager]$ ruby ./configure.rb
/usr/share/ruby/vendor_ruby/2.0/rubygems/core_ext/kernel_require.rb:55:in require': cannot load such file -- io/console (LoadError) from /usr/share/ruby/vendor_ruby/2.0/rubygems/core_ext/kernel_require.rb:55:inrequire'
from /home/ec2-user/schema-evolution-manager/lib/schema-evolution-manager.rb:3:in <top (required)>' from ./configure.rb:16:inload'
from ./configure.rb:16:in `

'

Support Redshift

Loving this tool for our Postgres 9.x ecosystem. We are beginning to roll out a multi-schema Redshift data warehouse, and I'm investigating this for adaptation. Looks like for our use case there are two major issues/enhancements needed:

  1. Remove requirement for PL/pgSQL. (Redshift doesn't support it or any user defined functions.) Aside from the bootstrap s-e-o data model I don't see it in use.
  2. Support multiple schemas. We are using Postgres schemas for multi-tenancy and would need to apply changes across one or all schemas in a database.

I can work on these if it's something you are interested in, but wanted your input first. Thanks!

Apply Migration Scripts on docker startup

I am Unable to initialise database with schema evolution manager on docker startup. My docker file is as follows

FROM postgres:10-alpine

WORKDIR /opt/luminary-api-db
COPY . .

RUN apk add py-pip ruby ruby-rdoc
RUN gem install schema-evolution-manager && \
    pip install awscli

RUN cp docker-entrypoint-initdb.d/* /docker-entrypoint-initdb.d

EXPOSE 5432

docker-entrypoint-initdb.d contain one init script

set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE DATABASE ${POSTGRES_USER}_test;
    GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_USER}_test TO $POSTGRES_USER;
EOSQL

# Applying Sem Scripts
echo
echo 'PostgreSQL initialzing Data...'
echo

psql -U $POSTGRES_USER

sem-apply --url postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$POSTGRES_DB
sem-apply --url postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$POSTGRES_DB_test

On docker container startup the database is created fine but the schema evolution manager says its unable to connect to database
Here is the complete stack trace

/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sh
CREATE DATABASE
GRANT

PostgreSQL initialzing Data...

psql: could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP/IP connections on port 5432?
could not connect to server: Address not available
	Is the server running on host "localhost" (::1) and accepting
	TCP/IP connections on port 5432?
/usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/library.rb:136:in `rescue in system_or_error': Error running command[psql --no-align --tuples-only --no-psqlrc --command "select count(*) from pg_namespace where nspname='schema_evolution_manager'" postgresql://user:pass@localhost/db]: Non zero exit code[pid 47 exit 2] running command[psql --no-align --tuples-only --no-psqlrc --command "select count(*) from pg_namespace where nspname='schema_evolution_manager'" postgresql://user:pass@localhost/db] (RuntimeError)
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/library.rb:129:in `system_or_error'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/db.rb:32:in `psql_command'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/db.rb:90:in `schema_schema_evolution_manager_exists?'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/scripts.rb:86:in `scripts_previously_run'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/scripts.rb:48:in `each_pending'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/lib/schema-evolution-manager/db.rb:22:in `bootstrap!'
	from /usr/lib/ruby/gems/2.5.0/gems/schema-evolution-manager-0.9.39/bin/sem-apply:30:in `<top (required)>'
	from /usr/bin/sem-apply:23:in `load'
	from /usr/bin/sem-apply:23:in `<main>'

Can someone tell me how can i fix this issue?

Support nested directories

Common strategy is to have a 'schema' directory in the root of a repository.

However, using sem-add from the root puts the file in /scripts, rather than /schema/scripts

configure.rb should have defaults for git user

on a "pristine" machine with git installed but not configured for the current user configure.rb fails, looking at the template the git user is only used in a comment, it'd be nice if it didn't fail (by having a dummy comment, or avoid the comment, etc)

> ruby ./configure.rb
lib dir [/usr/local/lib]
bin dir [/usr/local/bin]
Writing ./install.rb
./lib/preconditions.rb:13:in `check_state': git user information not found. Run: (RuntimeError)
  git config --global user.name 'John Doe'
  git config --global user.email [email protected]
    from ./lib/library.rb:85:in `git_user'
    from ./lib/install_template.rb:12:in `generate'
    from ./lib/install_template.rb:22:in `write_to_file'
    from ./lib/install_template.rb:21:in `open'
    from ./lib/install_template.rb:21:in `write_to_file'
    from ./configure.rb:32

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.