Code Monkey home page Code Monkey logo

ecommerce's Introduction

ecommerce

A non-trivial application with DDD, CQRS and Event Sourcing built on Rails and Rails Event Store.

πŸ‘‰ ecommerce.arkademy.dev

imgur

The Big Vision

This project has several high-level goals:

  • βœ…to show that it's possible to modularize a non-trivial Rails app
  • βœ…to show that DDD is possible in Rails
  • βœ…to show that CQRS is possible in Rails
  • βœ…to show that Event Sourcing is possible in Rails
  • βœ…to show that it's possible to use RailsEventStore in a non-trivial app
  • βœ…to show that User class doesn't have to be the center of the universe
  • βœ…to serve as an example of a DDD project (not only in Rails)
  • βœ…to let people play with this codebase to get a feel if DDD is for them
  • βœ…to show that tests can be fast if the app is well modularized
  • βœ…to show a proper pyramid of tests
  • βœ…to teach event-driven architectures
  • βœ…to show how to use RailsEventStore
  • βœ…to bring DDD enthusiasts from .Net/Java/PHP/others to the Ruby world 😎
  • βœ…to popularize professional testing techniques - mutation testing
  • βœ…to allow programmers to reuse existing and popular domains (shown in pricing_catalog app)
  • to build new apps like Lego
  • to have reusable SaaS components
  • to be a good SaaS starter app

Domains

Event storming (events and commands) - Miro documentation

Domains exist in directories starting at ecommerce.

ecommerce/
β”œβ”€β”€ crm
β”œβ”€β”€ inventory
β”œβ”€β”€ invoicing
β”œβ”€β”€ ordering
β”œβ”€β”€ payments
β”œβ”€β”€ pricing
β”œβ”€β”€ processes
β”œβ”€β”€ product_catalog
β”œβ”€β”€ shipping
└── taxes

(almost) Each one has a README introduction:

Application

Order management application lives at rails_application directory.

This application simulates a process of managing orders.

We start with a list of exiting products, customers and coupons (populated with seeds).

UI flow

Customer perspective

The customer perspective is "simulated" only - via using the select box.

  • Add/remove some of the existing products to the order
  • Choose the customer
  • Submit the order
    • after this you can't update the order items or customer
    • it generates the order number like "2021/03/20" (the last part is random(100))
  • Look at the order
  • Look at the history of events (in the Rails Event Store Browser)

Read models

Read models are application specific so they live at the application level. In our case it's in the main ecommerce Rails app:

Read models

Additionally, we have created the Pricing Catalog application that is a separate Rails application. It's a good example of how to create a separate application that uses the same domains, but has different read models.

Process Managers

Release payments when order expired

There's a process manager responsible for dealing with the process of expiring orders.

It takes the following events as the input:

  • Ordering::OrderPlaced
  • Ordering::OrderExpired
  • Ordering::OrderConfirmed
  • Payments::PaymentAuthorized
  • Payments::PaymentReleased

When certain conditions are met the process manager return a ReleasePayment command.

Confirm order when payment successful

Another process manager is responsible for confirming order. It does it, when a successful payment is detected.

Contributing guide

We welcome all the contributors here.

As you see, this project is not an usual Rails project. Many Rails conventions are not followed here. Usually there's a good reason for that. Trust us, but feel free to challenge our decisions.

Code comments

Do not leave comments in the code by default. Just leave an GitHub issue instead of a TODO comment for example.

Technical debt

One of the goals of this project is educational - to show how to implement certain features with DDD. As such we treat the actual code as business value. That's why (as opposed to "normal" projects") we can treat technical debt as part of the "backlog".

Whenever you add a temporary hack to the codebase, add a Github issue - maybe someone else will be able to help or clean. When you work on certain are and see some not pretty code - fix it or create an issue and add the "debt" label.

Backlog

If you're somehow experienced with ecommerce (even when not as a dev) - your experience can help us. Please create new Github issues with features that can exist in typical ecommerces. Our goal is to cover as many features as possible - especially the tricky ones.

If you're a developer working on ecommerce and you want to learn how to implement a specific feature - feel free to add this as a ticket too.

All tickets should bring business value and be consistent with previous features. It's fine to create vague tickets at the beginning and let them be more specific later.

Local setup

As for the local dev setup:

  • we use Makefile, so make install should simplify a lot
  • to start the web application and all the workers type make dev
  • there's docker-compose in the rails_application, if you're into Docker
  • Ruby version, it's best to use the same, which we use for CI/production: as defined in this file

Bundler note

Please check that your bundler version is not ancient, and up to date. For more details check: git show 6dc6e1c2ea833e1ea5821cc9bc9bd5dfadbfda9a which explains the problem and proposes a solution. A sign that you have a problem will be unusual changes in Gemfile.lock i.e. changes in remote sources and placement of gems definitions.

Things worth knowing about:

  • Zeitwerk is not used here - you need to be explicit with requires
  • Any IDE should work with this codebase
  • We use mutant for mutation testing, code that is untested breaks the build.
  • But sometimes we allow ourselves for mutant ignores (explicit mark for mutant to ignore)
  • Mutant ignores are considered technical debt.
  • REST is not followed here for routes, no need for that
  • Certain ideas are in the Work In Progress status - sorry about that
  • Feel free to ask.
  • We keep domains/BC code decoupled from each other - with the long term goal of reusability.
  • After you make a contribution, we'll invite you to a special Discord
  • Contributing is a good way to learn DDD/CQRS/Event sourcing.

Discord

There's a Discord server connected with RailsEventStore and with this Ecommerce project. Feel free to join here to ask questions or discuss the vision.

I like it, where can I learn more about all those DDD concepts?

Over time we have developed a number of DDD-related online courses. We now sell them as part of one membership access via arkademy.dev.

ecommerce's People

Contributors

ag4ta avatar andrzejkrzywda avatar antonpaisov avatar bbuchalter avatar bingerxd avatar bryszard avatar caws avatar dependabot-support avatar dependabot[bot] avatar fidel avatar fpacanowski avatar hugohernani avatar israelb avatar jandudulski avatar kennethkalmer avatar krzykamil avatar lukaszreszke avatar mostlyobvious avatar mpraglowski avatar ortegacmanuel avatar pansarin avatar pjurewicz avatar porbas avatar pstrzalk avatar saksham-jain avatar sardaukar avatar stolarczykt avatar swistak35 avatar tomaszpatrzek avatar wlk 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

ecommerce's Issues

Create dedicated orders view for client

Currently, when a user logs in as a specific client /client they see a list of submitted orders. Paid, Expired and Cancelled orders are also visible. The user has the possibility to navigate to a specific order and see its details by clicking the order's number.

Then they're redirected to that order at /orders/id URL. The /orders URL is dedicated to being used by the shops' staff, not the client itself.

Therefore new view should be created for the client to see its orders. The view should contain ordered items and their final price.

Archiving old orders

The orders screen becomes messy after some time.

Let's allow to archive specific orders - they would then disappear from the main screen.

Typical ecommerce cart

Right now the system is more an order management system. Let's build a simple cart system. The simplest possible. Just adding/removing from cart. Confirming. Later we will extend it.

Show the order history - with RES browser

The idea here is to use RES browser. It's already used but the stream which we show doesn't contain all the data.

We can show the Orders read model as a stream (it's not yet a stream, but we can make it a stream). This would work, but it feels a bit like coupling this stream for two purposes:

  • building the Orders data structure for the client facing UI
  • use it in RES browser

Maybe as the first step, this coupling is fine.

Creating an explicit stream per read model sounds like a very good thing to do, anyway. This will simplify the process of rebuilding orders and show it as an example education-wise.

Display Shipments

Let's create a new menu item called "Shipments".

A list of those orders which have Shipment assigned will appear.

Probably we only need to react to "Shipment*" events.
The output would be an ActiveRecord model ShipmentsList::Shipment with shipment_id/order_id, address, status.

Better uniqueness validation for coupons and happy hours

Currently the validation for uniqueness on registering coupons and creating happy hours is very simple - based on id only. If there is a coupon with a same id, the aggregate will mark it as @registered = true and raise an AlreadyRegistered error when somebody tries to register is again.

You can see it here - https://github.com/RailsEventStore/ecommerce/blob/0c4d8d0305c4ee6554e64789c32451c7ad826e81/ecommerce/pricing/lib/pricing/coupon.rb

It would be good to have some other uniqueness validation - eg. on the code attribute (in both coupons and happy hours). I'm not telling where such validation should be added. I'd say this is part of the exercise to decide about it.

Ordering::Order has lost its invariant

Moving ItemAddedToBasket and ItemRemovedFromBasket events to the Pricing BC caused that Ordering::Order can no longer protect its business rule of not adding items after order submission.

There had been raise AlreadySubmitted unless @state.equal?(:draft) before

Changing order discounts

Sometimes a client may negotiate well and try to improve the discount. Let's allow it to happen.

Integration tests shouldn't use commands for setup

In ideal world integration tests shouldn't use commands. Commands are hidden as the black box in the application.
Integration tests are the top of the testing pyramid and should assume as minimal internals as possible.

We should test via the HTTP layer instead.

Sometimes, when for some reason, certain data changes are not possible via the http, then using commands is allowed.

Also, when a feature is in progress, it might be a good idea to start with commands in the integration test but over time replace with "UI simulation".

Change HappyHour into TimePromotion

The first implementation of HappyHour is not very useful in the context of ecommerce - it is a daily recurring promotion on products, while in ecommerce it's a rare use case. More often we need a one-time promotion constrained in time.

At the same time, this implementation had other flaws. It was:

  1. Using too big command
  2. Accessing internal data of an aggregate
  3. Reusing Products::Product read model for the context of the Happy Hour UI

As the next iteration we want to change it, simplify and get rid of some flaws at the same time.

The change - we will have TimePromotion instead of HappyHour.
It won't be a recurring promotion (for now), but just bounded by the start_time and end_time.
It won't be checking if there is an overlapping promotion, promotions can be stacked.
It won't be applied to a specific product, but rather to all products.

Unit tests for processes

At the moment the processes are testes mostly via the integration tests.
Also, they are ignored by mutant (almost always a technical debt).

The goal of this ticket is to cover our processes with unit tests.

I'd aim for unit tests where events are the input and commands are the expected output.
However, they do have the drawback of risking being out of sync (when the published events are changed), but I hope this will be caught at integration level.

To whowever is interested in helping here, feel free to approach the process one by one. Just create a ticket per each process.

Waitlist

Waitlist

Short description

This feature allows customers to "subscribe" to a product.

For the shop owner it allows to sell more and increase customer loyalty.

Impact Mapping

Which target groups are impacted?

Customers interested in a specific product which is not available at the moment.

It might be a product which they bought already (and a product which makes sense to buy regularly). This means it targets existing clients.

Also, new clients who are simply interested in that one product.

How do we want to change their behaviour?

Instead of silently leaving the shop, we want them to subscribe for the product.

How this feature makes more money?

We can look which products are mostly waited on and introduce them to the offer without a risk, potentially with a bigger margin.

We sell more stuff at a bigger margin.

We collect more emails.

Start from the middle

Add a link on a ProductPage - notify me when available.

When logged in client, show green confirmation. If not logged in - show link to login or field for email (without logging in).

Schedule an email when a product is available.

Middle is ready - next steps

Add link also to ProductList pages.

When notifying by email present a link to a cart with this item in the cart.

Add notifications system to the store and show unread notification there.

Open questions

  • Wait/subscribe to a combo of products - only then be notified when all are available

Testing

What are the acceptance scenarios?

  • Add product to offer
  • Make it unavailable
  • Log in as existing client (or present email field)
  • "notify me"
  • Enable product
  • assert email was sent with the right link

Simplify reusability of existing bounded contexts

We want to reuse some of the existing BCs.

Gathering them in one directory sounds like a good step forward. Also, this may be a good experiment in the popular topic of "in what directories should we put the domain code".

Type streams empty?

Running the demo, I see that even after creating several order, all type streams are empty. What am I doing wrong? Example for one of the types:

image

image

image

Get rid of the technical debt in OrdersController#update_discount

Recently I’ve introduced technical debt by adding not so nice the if statement to the update_discount action:

def update_discount
@order_id = params[:id]
if Orders::Order.find_by_uid(params[:id]).percentage_discount
command_bus.(
Pricing::ChangePercentageDiscount.new(
order_id: @order_id,
amount: params[:amount]
)
)
else
command_bus.(
Pricing::SetPercentageDiscount.new(
order_id: @order_id,
amount: params[:amount]
)
)
end
redirect_to edit_order_path(@order_id)
end

We would like to improve this code and make tech debt disappear.

Issues to resolve:

  • dependency on a read model
  • if statement

All suggestions on how to resolve this issue are greatly appreciated!

CI: run only tests of the changed components

With the current high level of decoupling between our components (BC, read models, processes) it feels like we could speedup CI/CD process by only running those component tests which could potentially be impacted by the change.

Anyone knows how to start with this?

Client Authentication

Currently, a client can "log in" by navigating to /client URL, selecting desired client name, and clicking the login button.
The outcome of this issue should be a possibility to log in using the client login and password.

Separate context from application

Rails application is a web delivery mechanism for this ecommerce app. The contexts can live above the application to signify their independence. This can also draw greater line between write and read models β€” there they belong.

What's more β€” we can reuse contexts in applications based on different frameworks. Or even in different applications.

Cancel Order as UI button

It used to be the part of admin panel. Later it was removed, but the code under the hood is still there and tested.
Let's add this feature to the UI.

3+1 gratis

This feature allows customers to get 1 item free when they buy 3 items.

Duplicate products and customers

Steps to reproduce:
Setup database and then for example run rake db: seed a couple of times (seeds are using commands)
2022-05-19-152850_1197x864_scrot
There is some code that is supposed to raise an error, and it even has a test, but it seems the test are false positives
2022-05-19-153136_1463x814_scrot
Both the products and customers have multiple duplicates (same attributes, different ids). Seems resonable that this state should not be allowed

Removing order discounts

Currently we allow setting orders discount via the UI. It would be nice to be able to remove them

Happy hour

This feature allows setting a certain period of time when a price of a certain product (or a list of products) is different.

Client panel - see my orders

At the moment we have some hardcoded clients for whom we can create orders.

This issue is meant to be a small step towards a more typical ecommerce where clients can create their orders.
However, for now let's just start with displaying the existing orders for the specific client.

Authentication could be part of this ticket, but we can also have a temporary shortcut of "selecting" the client which we want to log-in as.

It could be a new route like /client and then a new read model - ClientOrders.

Ensure a customer exists before an order can be submitted or confirmed

As of recently, we've only had a check if customer id is not nil. What was missing and still is missing is to check whether a customer exists.

In a way, the check is there but only in the form of a read model coupling:
order.customer = Customer.find(event.data[:customer_id]).name

One option is to create a check in the SubmitOrder cmd handler. Another (better?) would be to turn the OrderSubmission process into actual process manager. This resembles a typical "checklist" characteristical to process managers.

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.