Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
olek committed May 3, 2015
1 parent 43dc761 commit 6eba874
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 59 deletions.
137 changes: 78 additions & 59 deletions README.md
Expand Up @@ -4,33 +4,30 @@

## Synopsis

Persistence isolation framework for long term ruby projects.

Or, "Half, not Half-Assed" approach to ORM.
ORM with a functional twist, or persistence isolation framework, choose one that
appeals to you more.

## Caution

ORMivore is highly opinionated and does not quite follow conventional
rules of wisdom in ruby/rails community.
ORMivore is highly opinionated and does not follow
conventions typically found in Rails projects.

## Audience

People that value ability to maintain app after it is couple years old,
or want to get legacy app back under control.

On the other hand, if quick throw-away projects that require no
extension/maintenance in the future are the story of your life,
On the other hand, if quick-and-dirty projects that require no
extension/maintenance in the future are what you do,
probably ORMivore is not for you.

## Stability

ORMivore is in the R&D 'alpha' stage. In other words, it is playground
for experimentation. Changes are highly likely to be not backward
for experimentation. Changes are highly likely to not be backward
compatible. If you decide to use it, prepare to roll up your sleeves,
and I disclaim any potential indirect harm to kittens.

Just to state the obvious - ORMivore is not production ready just yet.

## Motivation

If you have seen legacy Rails app, chances are you noticed:
Expand All @@ -40,25 +37,29 @@ If you have seen legacy Rails app, chances are you noticed:
- copious amounts of logged SQL that is hard to relate back to application code
- bad performance and high database load caused by runaway (and inefficient) SQL
queries
- hard to find bugs caused by multiple instances of object with same
identity

ORMivore is designed to help in either avoiding those issues on new
project, or slowly eliminating them on a legacy app.

## Philosophy summary
## Inspirational ideas

- Everything the system does FOR you, the system also does TO you
- Actions that will lead to long term pain better be painful
- Actions that lead to long term pain better be painful
immediately, and never sugar-coated with short term gain
- Long term maintenability trumps supersonic speed of initial development
- There is much to be learned from functional programming
- Functional programming is good and you can mix and match it with OO
- Immutability promotes better sleep at night
- So much complexity in software comes from trying to make one thing do
two things (Ryan Singer)
two things

## Philosophy explained
## Driving principles

OOD is great, but have to be applied with care. ORMivore uses plain data
structures (functional style) to communicate between different layers,
solving problem of messy circular dependencies.
OOD is great, but have to be applied with care, and is not universal.
ORMivore uses plain data structures (functional style) to communicate
between different layers, solving problem of messy circular
dependencies.

Mutable entities have state, and that makes reasoning about them more
difficult. Immutable entities are easy to reason about and discourage
Expand All @@ -67,55 +68,61 @@ placing any "how to" behavior on them, making them a good place for
multi-threaded environments.

Many ORM make it too easy to conflate business logic and persistence.
Ports and adapters pattern (hexagonal architecture) are core of
ORMivore, and it is great for isolating persistance logic from entities.
It also allows for some degree of substitutability of different storages.
Ports and adapters pattern (hexagonal architecture) is core of
ORMivore, and it helps to isolate persistance logic from entities.
It also allows for pluggable storage mechanisms (to some degree).

Putting responsibility of managing data and managing association in one
same object leads to many lies inside of code that quickly relult in a
lot of complexity. It is much simpler to divide those responsibilities
(not easier, but much simpler).
same object results in a tangled ball of complexity. Dividing those
responsibilities allows for a much simpler system.

While STI and polymorphic relations are bad for your health, your legacy
While STI and polymorphic relations are not such a good idea, your legacy
database is probably littered with them, and ORMivore provides means to
map those to domain objects in a 'class-less' way.
That makes it possible to introduce ORMivore entities in the same
project/process as legacy ActiveRecord models.
map those to domain objects of different classes.
That makes it possible to introduce ORMivore entities in the legacy
project side-to-side with old ActiveRecord models.

## Installation

Just add 'gem "ormivore"' to Gemfile, or run 'gem install ormivore'.
Just add 'gem "ormivore"' to your Gemfile, or run 'gem install ormivore'.

## Basic Code Example

Typical setup would include a bit of boiler plate code that would look a
little ridiculous for such simple example heere. This example is using
shortcuts that generate boiler plate classes automatically, but for real
production code spelling things out probably is better, it will come
production code spelling details out is better, those extra classes come
handy when real functionality and tests are added.

This is complete example that can be copy/pasted in the console, and
will work. It uses memory adapter to avoid having to configure database
access, but code change to get it to work with SQL database is trivial.
This is complete working example that can be copy/pasted in irb session.
It uses memory adapter to avoid having to configure database
access, but it is trivial to change to it to work with SQL database.

Oh, and here is convenient way to run irb session when you are in ORMivore project:

irb -r ./app/console.rb

```ruby

Sample = Module.new

ORMivore::create_entity_skeleton(Sample, :post, port: true, repo: true, memory_adapter: true) do
ORMivore::create_entity_skeleton(Sample, :post,
port: true, repo: true, memory_adapter: true) do
attributes do
string :title
string :body
end
end

ORMivore::create_entity_skeleton(Sample, :tag, port: true, repo: true, memory_adapter: true) do
ORMivore::create_entity_skeleton(Sample, :tag,
port: true, repo: true, memory_adapter: true) do
attributes do
string :name
end
end

ORMivore::create_entity_skeleton(Sample, :tagging, port: true, repo: true, memory_adapter: true) do
ORMivore::create_entity_skeleton(Sample, :tagging,
port: true, repo: true, memory_adapter: true) do
attributes do
integer :post_id
integer :tag_id
Expand Down Expand Up @@ -152,26 +159,41 @@ module Sample
extend ORMivore::RepoFamily
end

Post::Repo.new(Post::Entity, Post::StoragePort.new(Post::StorageMemoryAdapter.new), family: Repos)
Tag::Repo.new(Tag::Entity, Tag::StoragePort.new(Tag::StorageMemoryAdapter.new), family: Repos)
Tagging::Repo.new(Tagging::Entity, Tagging::StoragePort.new(Tagging::StorageMemoryAdapter.new), family: Repos)
Post::Repo.new(Post::Entity,
Post::StoragePort.new(Post::StorageMemoryAdapter.new), family: Repos)
Tag::Repo.new(Tag::Entity,
Tag::StoragePort.new(Tag::StorageMemoryAdapter.new), family: Repos)
Tagging::Repo.new(Tagging::Entity,
Tagging::StoragePort.new(Tagging::StorageMemoryAdapter.new), family: Repos)

Repos.freeze
end

session = ORMivore::Session.new(Sample::Repos, Sample::Associations) # #<ORMivore::Session:0x7ffe1beef7c0>
post = session.repo.post.create(title: 'foo', body: 'bar') # #<Sample::Post::Entity derived attributes={:title=>"foo", :body=>"bar"}>
post = post.apply(body: 'baz') # #<Sample::Post::Entity derived attributes={:title=>"foo", :body=>"baz"}>
session.association(post, :tags).values # []
t1 = session.repo.tag.create(name: 't1') # #<Sample::Tag::Entity derived attributes={:name=>"t1"}>
session.association(post, :tags).add(t1) # [#<Sample::Tagging::Entity derived attributes={:post_id=>-1, :tag_id=>-1}>]
session.association(post, :tags).values # [#<Sample::Tag::Entity derived attributes={:name=>"t1"}>]
session.association(post, :taggings).values # [#<Sample::Tagging::Entity derived attributes={:post_id=>-1, :tag_id=>-1}>]
session.commit # [Sample::Post::Entity, Sample::Tag::Entity, Sample::Tagging::Entity]

session = ORMivore::Session.new(Sample::Repos, Sample::Associations) # #<ORMivore::Session:0x7ffe1be699e0>
post = session.repo.post.find_by_id(1) # #<Sample::Post::Entity root id=1 attributes={:title=>"foo", :body=>"baz"}>
session.association(post, :tags).values # [#<Sample::Tag::Entity root id=1 attributes={:name=>"t1"}>]
session.association(post, :taggings).values # [#<Sample::Tagging::Entity root id=1 attributes={:post_id=>1, :tag_id=>1}>]
session = ORMivore::Session.new(Sample::Repos, Sample::Associations)
# => #<ORMivore::Session:0x7ffe1beef7c0>
post = session.repo.post.create(title: 'foo', body: 'bar')
# => #<Sample::Post::Entity derived attributes={:title=>"foo", :body=>"bar"}>
post.apply(body: 'baz')
# => #<Sample::Post::Entity derived attributes={:title=>"foo", :body=>"baz"}>
session.association(post, :tags).values
# => []
t1 = session.repo.tag.create(name: 't1')
# => #<Sample::Tag::Entity derived attributes={:name=>"t1"}>
session.association(post, :tags).add(t1)
# => [#<Sample::Tagging::Entity derived attributes={:post_id=>-1, :tag_id=>-1}>]
session.association(post, :tags).values
# => [#<Sample::Tag::Entity derived attributes={:name=>"t1"}>]
session.association(post, :taggings).values
# => [#<Sample::Tagging::Entity derived attributes={:post_id=>-1, :tag_id=>-1}>]
session.commit_and_reset
# => [Sample::Post::Entity, Sample::Tag::Entity, Sample::Tagging::Entity]

post = session.repo.post.find_by_id(post.identity)
# => #<Sample::Post::Entity root id=1 attributes={:title=>"foo", :body=>"baz"}>
session.association(post, :tags).values
# => [#<Sample::Tag::Entity root id=1 attributes={:name=>"t1"}>]
session.association(post, :taggings).values
# => [#<Sample::Tagging::Entity root id=1 attributes={:post_id=>1, :tag_id=>1}>]


```
Expand All @@ -180,7 +202,7 @@ session.association(post, :taggings).values # [#<Sample::Tagging::Entity root id

Check integration specs for more in depth examples. Also...
Monologue blog app is in the process of being ported to ORMivore, check
it out (it is work in progress).
it out at https://github.com/olek/monologue

## Tests

Expand All @@ -198,9 +220,7 @@ bundle exec rake spec
## Contributors

At this point, this is the playground of experimentation, and it does
not have to be just mine! Forks and pull requests are welcome. Of
course, I reserve the right to politely decline or ruthlessly
'refactor'.
not have to be just mine! Forks and pull requests are welcome.

## License

Expand All @@ -213,5 +233,4 @@ MIT
This README is certainly not enough, but it is indeed better than nothing.

English is not my 'mother tongue'. I am sure this document is littered
with mistaekes. If your english is any better than mine - please help me
fix them.
with mistaekes. Help me fix them.
1 change: 1 addition & 0 deletions lib/ormivore/session.rb
Expand Up @@ -119,6 +119,7 @@ def commit
SillySessionPersistenceStrategy.new(self).call
end

# TODO take care of duplication
def commit_and_reset
commit

Expand Down

0 comments on commit 6eba874

Please sign in to comment.