diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..bdaa524 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,273 @@ +.highlight table td { padding: 5px; } + +.highlight table pre { margin: 0; } + +.highlight .cm { color: #777772; font-style: italic; } + +.highlight .cp { color: #797676; font-weight: bold; } + +.highlight .c1 { color: #777772; font-style: italic; } + +.highlight .cs { color: #797676; font-weight: bold; font-style: italic; } + +.highlight .c, .highlight .cd { color: #777772; font-style: italic; } + +.highlight .err { color: #a61717; background-color: #e3d2d2; } + +.highlight .gd { color: #000000; background-color: #ffdddd; } + +.highlight .ge { color: #000000; font-style: italic; } + +.highlight .gr { color: #aa0000; } + +.highlight .gh { color: #797676; } + +.highlight .gi { color: #000000; background-color: #ddffdd; } + +.highlight .go { color: #888888; } + +.highlight .gp { color: #555555; } + +.highlight .gs { font-weight: bold; } + +.highlight .gu { color: #aaaaaa; } + +.highlight .gt { color: #aa0000; } + +.highlight .kc { color: #000000; font-weight: bold; } + +.highlight .kd { color: #000000; font-weight: bold; } + +.highlight .kn { color: #000000; font-weight: bold; } + +.highlight .kp { color: #000000; font-weight: bold; } + +.highlight .kr { color: #000000; font-weight: bold; } + +.highlight .kt { color: #445588; font-weight: bold; } + +.highlight .k, .highlight .kv { color: #000000; font-weight: bold; } + +.highlight .mf { color: #009999; } + +.highlight .mh { color: #009999; } + +.highlight .il { color: #009999; } + +.highlight .mi { color: #009999; } + +.highlight .mo { color: #009999; } + +.highlight .m, .highlight .mb, .highlight .mx { color: #009999; } + +.highlight .sb { color: #d14; } + +.highlight .sc { color: #d14; } + +.highlight .sd { color: #d14; } + +.highlight .s2 { color: #d14; } + +.highlight .se { color: #d14; } + +.highlight .sh { color: #d14; } + +.highlight .si { color: #d14; } + +.highlight .sx { color: #d14; } + +.highlight .sr { color: #009926; } + +.highlight .s1 { color: #d14; } + +.highlight .ss { color: #990073; } + +.highlight .s { color: #d14; } + +.highlight .na { color: #008080; } + +.highlight .bp { color: #797676; } + +.highlight .nb { color: #0086B3; } + +.highlight .nc { color: #445588; font-weight: bold; } + +.highlight .no { color: #008080; } + +.highlight .nd { color: #3c5d5d; font-weight: bold; } + +.highlight .ni { color: #800080; } + +.highlight .ne { color: #990000; font-weight: bold; } + +.highlight .nf { color: #990000; font-weight: bold; } + +.highlight .nl { color: #990000; font-weight: bold; } + +.highlight .nn { color: #555555; } + +.highlight .nt { color: #000080; } + +.highlight .vc { color: #008080; } + +.highlight .vg { color: #008080; } + +.highlight .vi { color: #008080; } + +.highlight .nv { color: #008080; } + +.highlight .ow { color: #000000; font-weight: bold; } + +.highlight .o { color: #000000; font-weight: bold; } + +.highlight .w { color: #bbbbbb; } + +.highlight { background-color: #f8f8f8; } + +/******************************************************************************* +MeyerWeb Reset +*******************************************************************************/ +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; vertical-align: baseline; } + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } + +ol, ul { list-style: none; } + +table { border-collapse: collapse; border-spacing: 0; } + +/******************************************************************************* +Theme Styles +*******************************************************************************/ +body { box-sizing: border-box; color: #373737; background: #212121; font-size: 16px; font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; line-height: 1.5; -webkit-font-smoothing: antialiased; } + +h1, h2, h3, h4, h5, h6 { margin: 10px 0; font-weight: 700; color: #222222; font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; letter-spacing: -1px; } + +h1 { font-size: 36px; font-weight: 700; } + +h2 { padding-bottom: 10px; font-size: 32px; background: url("../images/bg_hr.png") repeat-x bottom; } + +h3 { font-size: 24px; } + +h4 { font-size: 21px; } + +h5 { font-size: 18px; } + +h6 { font-size: 16px; } + +p { margin: 10px 0 15px 0; } + +footer p { color: #f2f2f2; } + +a { text-decoration: none; color: #0F79D0; text-shadow: none; transition: color 0.5s ease; transition: text-shadow 0.5s ease; -webkit-transition: color 0.5s ease; -webkit-transition: text-shadow 0.5s ease; -moz-transition: color 0.5s ease; -moz-transition: text-shadow 0.5s ease; -o-transition: color 0.5s ease; -o-transition: text-shadow 0.5s ease; -ms-transition: color 0.5s ease; -ms-transition: text-shadow 0.5s ease; } + +a:hover, a:focus { text-decoration: underline; } + +footer a { color: #F2F2F2; text-decoration: underline; } + +em, cite { font-style: italic; } + +strong { font-weight: bold; } + +img { position: relative; margin: 0 auto; max-width: 739px; padding: 5px; margin: 10px 0 10px 0; border: 1px solid #ebebeb; box-shadow: 0 0 5px #ebebeb; -webkit-box-shadow: 0 0 5px #ebebeb; -moz-box-shadow: 0 0 5px #ebebeb; -o-box-shadow: 0 0 5px #ebebeb; -ms-box-shadow: 0 0 5px #ebebeb; } + +p img { display: inline; margin: 0; padding: 0; vertical-align: middle; text-align: center; border: none; } + +pre, code { color: #222; background-color: #fff; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; font-size: 14px; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } + +pre { padding: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); overflow: auto; } + +code { padding: 3px; margin: 0 3px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } + +pre code { display: block; box-shadow: none; } + +blockquote { color: #666; margin-bottom: 20px; padding: 0 0 0 20px; border-left: 3px solid #bbb; } + +ul, ol, dl { margin-bottom: 15px; } + +ul { list-style-position: inside; list-style: disc; padding-left: 20px; } + +ol { list-style-position: inside; list-style: decimal; padding-left: 20px; } + +dl dt { font-weight: bold; } + +dl dd { padding-left: 20px; font-style: italic; } + +dl p { padding-left: 20px; font-style: italic; } + +hr { height: 1px; margin-bottom: 5px; border: none; background: url("../images/bg_hr.png") repeat-x center; } + +table { border: 1px solid #373737; margin-bottom: 20px; text-align: left; } + +th { font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; padding: 10px; background: #373737; color: #fff; } + +td { padding: 10px; border: 1px solid #373737; } + +form { background: #f2f2f2; padding: 20px; } + +/******************************************************************************* +Full-Width Styles +*******************************************************************************/ +.outer { width: 100%; } + +.inner { position: relative; max-width: 640px; padding: 20px 10px; margin: 0 auto; } + +#forkme_banner { display: block; position: absolute; top: 0; right: 10px; z-index: 10; padding: 10px 50px 10px 10px; color: #fff; background: url("../images/blacktocat.png") #0090ff no-repeat 95% 50%; font-weight: 700; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } + +#header_wrap { background: #212121; background: -moz-linear-gradient(top, #373737, #212121); background: -webkit-linear-gradient(top, #373737, #212121); background: -ms-linear-gradient(top, #373737, #212121); background: -o-linear-gradient(top, #373737, #212121); background: linear-gradient(to top, #373737, #212121); } + +#header_wrap .inner { padding: 50px 10px 30px 10px; } + +#project_title { margin: 0; color: #fff; font-size: 42px; font-weight: 700; text-shadow: #111 0px 0px 10px; } + +#project_tagline { color: #fff; font-size: 24px; font-weight: 300; background: none; text-shadow: #111 0px 0px 10px; } + +#downloads { position: absolute; width: 210px; z-index: 10; bottom: -40px; right: 0; height: 70px; background: url("../images/icon_download.png") no-repeat 0% 90%; } + +.zip_download_link { display: block; float: right; width: 90px; height: 70px; text-indent: -5000px; overflow: hidden; background: url(../images/sprite_download.png) no-repeat bottom left; } + +.tar_download_link { display: block; float: right; width: 90px; height: 70px; text-indent: -5000px; overflow: hidden; background: url(../images/sprite_download.png) no-repeat bottom right; margin-left: 10px; } + +.zip_download_link:hover { background: url(../images/sprite_download.png) no-repeat top left; } + +.tar_download_link:hover { background: url(../images/sprite_download.png) no-repeat top right; } + +#main_content_wrap { background: #f2f2f2; border-top: 1px solid #111; border-bottom: 1px solid #111; } + +#main_content { padding-top: 40px; } + +#footer_wrap { background: #212121; } + +/******************************************************************************* +Small Device Styles +*******************************************************************************/ +@media screen and (max-width: 992px) { img { max-width: 100%; } } +@media screen and (max-width: 480px) { body { font-size: 14px; } + #downloads { display: none; } + .inner { min-width: 320px; max-width: 480px; } + #project_title { font-size: 32px; } + h1 { font-size: 28px; } + h2 { font-size: 24px; } + h3 { font-size: 21px; } + h4 { font-size: 18px; } + h5 { font-size: 14px; } + h6 { font-size: 12px; } + code, pre { font-size: 11px; } } +@media screen and (max-width: 320px) { body { font-size: 14px; } + #downloads { display: none; } + .inner { min-width: 240px; max-width: 320px; } + #project_title { font-size: 28px; } + h1 { font-size: 24px; } + h2 { font-size: 21px; } + h3 { font-size: 18px; } + h4 { font-size: 16px; } + h5 { font-size: 14px; } + h6 { font-size: 12px; } + code, pre { min-width: 240px; max-width: 320px; font-size: 11px; } } +.inner { max-width: 960px; } + +pre, code { background-color: unset; font-size: unset; } + +code { font-size: 0.80em; } + +h1 > img { border: unset; box-shadow: unset; vertical-align: middle; -moz-box-shadow: unset; -o-box-shadow: unset; -ms-box-shadow: unset; } diff --git a/assets/images/bg_hr.png b/assets/images/bg_hr.png new file mode 100644 index 0000000..514aee5 Binary files /dev/null and b/assets/images/bg_hr.png differ diff --git a/assets/images/blacktocat.png b/assets/images/blacktocat.png new file mode 100644 index 0000000..e160053 Binary files /dev/null and b/assets/images/blacktocat.png differ diff --git a/assets/images/icon_download.png b/assets/images/icon_download.png new file mode 100644 index 0000000..5a793f1 Binary files /dev/null and b/assets/images/icon_download.png differ diff --git a/assets/images/sprite_download.png b/assets/images/sprite_download.png new file mode 100644 index 0000000..f9f8de2 Binary files /dev/null and b/assets/images/sprite_download.png differ diff --git a/assets/img/32.png b/assets/img/32.png new file mode 100644 index 0000000..6e9d2d1 Binary files /dev/null and b/assets/img/32.png differ diff --git a/assets/img/64.png b/assets/img/64.png new file mode 100644 index 0000000..83591d6 Binary files /dev/null and b/assets/img/64.png differ diff --git a/assets/img/Merq.ico b/assets/img/Merq.ico new file mode 100644 index 0000000..492cfe2 Binary files /dev/null and b/assets/img/Merq.ico differ diff --git a/assets/img/Merq.svg b/assets/img/Merq.svg new file mode 100644 index 0000000..3cb438e --- /dev/null +++ b/assets/img/Merq.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/img/async-sync-command-fix.png b/assets/img/async-sync-command-fix.png new file mode 100644 index 0000000..718b5fe Binary files /dev/null and b/assets/img/async-sync-command-fix.png differ diff --git a/assets/img/async-sync-command.png b/assets/img/async-sync-command.png new file mode 100644 index 0000000..60fcd90 Binary files /dev/null and b/assets/img/async-sync-command.png differ diff --git a/assets/img/command-interfaces.png b/assets/img/command-interfaces.png new file mode 100644 index 0000000..c819fe2 Binary files /dev/null and b/assets/img/command-interfaces.png differ diff --git a/assets/img/dotnet-counters.png b/assets/img/dotnet-counters.png new file mode 100644 index 0000000..6164356 Binary files /dev/null and b/assets/img/dotnet-counters.png differ diff --git a/assets/img/implement-icommand.png b/assets/img/implement-icommand.png new file mode 100644 index 0000000..bccd543 Binary files /dev/null and b/assets/img/implement-icommand.png differ diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000..1e69411 --- /dev/null +++ b/changelog.html @@ -0,0 +1,226 @@ + + + + + + + + + + +Changelog | Merq + + + + + + + + + + + + + + +
+
+ View on GitHub + +

Merq

+

Internal application architecture via command and event messages

+ + +
+
+ + +
+
+

Changelog

+ +

v2.0.0-rc.3 (2023-07-10)

+ +

Full Changelog

+ +

:bug: Fixed bugs:

+ + + +

v2.0.0-rc.2 (2023-07-10)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v2.0.0-rc.1 (2023-07-07)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v2.0.0-beta.4 (2023-07-06)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:bug: Fixed bugs:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v2.0.0-beta.3 (2022-11-19)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v2.0.0-beta.2 (2022-11-18)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

v2.0.0-alpha (2022-11-16)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:hammer: Other:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v1.3.0 (2022-07-28)

+ +

Full Changelog

+ +

v1.5.0 (2022-07-28)

+ +

Full Changelog

+ +

:sparkles: Implemented enhancements:

+ + + +

:twisted_rightwards_arrows: Merged:

+ + + +

v1.2.0-beta (2022-07-20)

+ +

Full Changelog

+ +

:twisted_rightwards_arrows: Merged:

+ + + +

v1.2.0-alpha (2022-07-16)

+ +

Full Changelog

+ +

* This Changelog was automatically generated by github_changelog_generator

+ +
+
+ + + + + + + diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..11453b5 --- /dev/null +++ b/changelog.md @@ -0,0 +1,141 @@ +# Changelog + +## [v2.0.0-rc.3](https://github.com/devlooped/Merq/tree/v2.0.0-rc.3) (2023-07-10) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-rc.2...v2.0.0-rc.3) + +:bug: Fixed bugs: + +- Fix automapper dependency [\#106](https://github.com/devlooped/Merq/pull/106) (@kzu) + +## [v2.0.0-rc.2](https://github.com/devlooped/Merq/tree/v2.0.0-rc.2) (2023-07-10) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-rc.1...v2.0.0-rc.2) + +:sparkles: Implemented enhancements: + +- Observability: add caller information to main API for improved telemetry [\#102](https://github.com/devlooped/Merq/issues/102) +- Avoid losing caller information when invoking extension methods [\#104](https://github.com/devlooped/Merq/pull/104) (@kzu) +- Add caller information to main API for improved telemetry [\#103](https://github.com/devlooped/Merq/pull/103) (@kzu) + +:twisted_rightwards_arrows: Merged: + +- Add public api analyzers to avoid inadvertent breaking changes [\#105](https://github.com/devlooped/Merq/pull/105) (@kzu) + +## [v2.0.0-rc.1](https://github.com/devlooped/Merq/tree/v2.0.0-rc.1) (2023-07-07) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-beta.4...v2.0.0-rc.1) + +:sparkles: Implemented enhancements: + +- Improve observability [\#99](https://github.com/devlooped/Merq/issues/99) +- Add command and event payloads to activity [\#101](https://github.com/devlooped/Merq/pull/101) (@kzu) +- Improve observability [\#100](https://github.com/devlooped/Merq/pull/100) (@kzu) + +## [v2.0.0-beta.4](https://github.com/devlooped/Merq/tree/v2.0.0-beta.4) (2023-07-06) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-beta.3...v2.0.0-beta.4) + +:sparkles: Implemented enhancements: + +- Preserve original exception stacktrace after recording telemetry error [\#79](https://github.com/devlooped/Merq/issues/79) + +:bug: Fixed bugs: + +- Don't allow mappers to map to same type [\#80](https://github.com/devlooped/Merq/issues/80) +- Set activity status to error when an exception is recorded [\#77](https://github.com/devlooped/Merq/issues/77) + +:twisted_rightwards_arrows: Merged: + +- Rename operations to Execute and Notify [\#97](https://github.com/devlooped/Merq/pull/97) (@kzu) +- Bump dependencies, switch to flexible central package versions [\#96](https://github.com/devlooped/Merq/pull/96) (@kzu) + +## [v2.0.0-beta.3](https://github.com/devlooped/Merq/tree/v2.0.0-beta.3) (2022-11-19) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-beta.2...v2.0.0-beta.3) + +:sparkles: Implemented enhancements: + +- Add exception telemetry recording [\#76](https://github.com/devlooped/Merq/issues/76) +- Add basic telemetry support in core message bus implementation [\#73](https://github.com/devlooped/Merq/issues/73) +- Issue a warning for non-public commands [\#71](https://github.com/devlooped/Merq/issues/71) +- Set activity status to error when an exception is recorded [\#78](https://github.com/devlooped/Merq/pull/78) (@kzu) +- Add basic telemetry support in core message bus implementation [\#74](https://github.com/devlooped/Merq/pull/74) (@kzu) +- Issue a warning for non-public commands [\#72](https://github.com/devlooped/Merq/pull/72) (@kzu) + +## [v2.0.0-beta.2](https://github.com/devlooped/Merq/tree/v2.0.0-beta.2) (2022-11-18) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v2.0.0-alpha...v2.0.0-beta.2) + +:sparkles: Implemented enhancements: + +- Fix support for non-void command handlers in duck typing [\#69](https://github.com/devlooped/Merq/issues/69) + +## [v2.0.0-alpha](https://github.com/devlooped/Merq/tree/v2.0.0-alpha) (2022-11-16) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v1.3.0...v2.0.0-alpha) + +:sparkles: Implemented enhancements: + +- Make duck-typing support pluggable with no built-in implementation [\#57](https://github.com/devlooped/Merq/issues/57) +- Add support for dynamic record creation including list-like properties [\#49](https://github.com/devlooped/Merq/issues/49) +- Support hierarchical record creation from generated dynamic factory [\#47](https://github.com/devlooped/Merq/issues/47) +- Provide analyzer + code fix for turning sync command to async and viceversa [\#38](https://github.com/devlooped/Merq/issues/38) +- Provide analyzers + code fixes for common command authoring errors [\#37](https://github.com/devlooped/Merq/issues/37) +- Add support for duck typing of commands [\#33](https://github.com/devlooped/Merq/issues/33) +- Add support for duck typing on events [\#31](https://github.com/devlooped/Merq/issues/31) +- Allow derived message bus implementation to monitor used event contracts [\#19](https://github.com/devlooped/Merq/issues/19) + +:hammer: Other: + +- Upgrade to centrally managed package versions [\#51](https://github.com/devlooped/Merq/issues/51) +- Add unit tests for analyzers [\#45](https://github.com/devlooped/Merq/issues/45) + +:twisted_rightwards_arrows: Merged: + +- Add pages and oss artifacts [\#62](https://github.com/devlooped/Merq/pull/62) (@kzu) +- Update to modern color [\#61](https://github.com/devlooped/Merq/pull/61) (@kzu) +- Make duck-typing support pluggable [\#60](https://github.com/devlooped/Merq/pull/60) (@kzu) +- Upgrade to centrally managed package versions [\#52](https://github.com/devlooped/Merq/pull/52) (@kzu) +- Add support for collections in dynamic record creation factories [\#50](https://github.com/devlooped/Merq/pull/50) (@kzu) +- Add support for hierarchical record creation from generated factories [\#48](https://github.com/devlooped/Merq/pull/48) (@kzu) +- Add comprehensive tests for analyzers and code fixes [\#46](https://github.com/devlooped/Merq/pull/46) (@kzu) +- Provide analyzer + code fix for turning sync command to async and viceversa [\#39](https://github.com/devlooped/Merq/pull/39) (@kzu) +- Support sync/async execute fixer on returning commands too [\#35](https://github.com/devlooped/Merq/pull/35) (@kzu) +- Duck typing of events and commands [\#34](https://github.com/devlooped/Merq/pull/34) (@kzu) +- Allow derived message bus implementation to monitor used event contracts [\#20](https://github.com/devlooped/Merq/pull/20) (@kzu) +- Unified IMessageBus interface [\#16](https://github.com/devlooped/Merq/pull/16) (@kzu) +- Add dynamic OS matrix [\#15](https://github.com/devlooped/Merq/pull/15) (@kzu) + +## [v1.3.0](https://github.com/devlooped/Merq/tree/v1.3.0) (2022-07-28) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v1.5.0...v1.3.0) + +## [v1.5.0](https://github.com/devlooped/Merq/tree/v1.5.0) (2022-07-28) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v1.2.0-beta...v1.5.0) + +:sparkles: Implemented enhancements: + +- Introduce IMessageBus unifying interface for Commands+Events [\#5](https://github.com/devlooped/Merq/issues/5) + +:twisted_rightwards_arrows: Merged: + +- Docs and packaging improvements [\#7](https://github.com/devlooped/Merq/pull/7) (@kzu) +- Introduce IMessageBus unifying interface for Commands+Events [\#6](https://github.com/devlooped/Merq/pull/6) (@kzu) + +## [v1.2.0-beta](https://github.com/devlooped/Merq/tree/v1.2.0-beta) (2022-07-20) + +[Full Changelog](https://github.com/devlooped/Merq/compare/v1.2.0-alpha...v1.2.0-beta) + +:twisted_rightwards_arrows: Merged: + +- Ensure we strong-name the assemblies with the previous key [\#4](https://github.com/devlooped/Merq/pull/4) (@kzu) + +## [v1.2.0-alpha](https://github.com/devlooped/Merq/tree/v1.2.0-alpha) (2022-07-16) + +[Full Changelog](https://github.com/devlooped/Merq/compare/9aed78c8a37c830093a8dbeb15981df3640dd350...v1.2.0-alpha) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/index.html b/index.html new file mode 100644 index 0000000..24620e9 --- /dev/null +++ b/index.html @@ -0,0 +1,491 @@ + + + + + + + + + + +Merq | Internal application architecture via command and event messages + + + + + + + + + + + + + + +
+
+ View on GitHub + +

Merq

+

Internal application architecture via command and event messages

+ + +
+
+ + +
+
+

Icon Merq

+ +

Version +Downloads +License

+ + +
+

Mercury: messenger of the Roman gods

+
+ +
+

Mercury > Merq-ry > Merq

+
+ +

Merq brings the Message Bus pattern together with +a command-oriented interface for an +extensible and decoupled in-process application architecture.

+ +

These patterns are well established in microservices and service oriented +architectures, but their benefits can be applied to apps too, especially +extensible ones where multiple teams can contribute extensions which +are composed at run-time.

+ +

The resulting improved decoupling between components makes it easier to evolve +them independently, while improving discoverability of available commands and +events. You can see this approach applied in the real world in +VSCode commands +and various events such as window events. +Clearly, in the case of VSCode, everything is in-process, but the benefits of +a clean and predictable API are pretty obvious.

+ +

Merq provides the same capabilities for .NET apps.

+ +

Events

+ +

Events can be any type, there is no restriction or interfaces you must implement. +Nowadays, C# record types +are a perfect fit for event data types. An example event could be a one-liner such as:

+ +
public record ItemShipped(string Id, DateTimeOffset Date);
+
+ +

The events-based API surface on the message bus is simple enough:

+ +
public interface IMessageBus
+{
+    void Notify<TEvent>(TEvent e);
+    IObservable<TEvent> Observe<TEvent>();
+}
+
+ +

By relying on IObservable<TEvent>, Merq integrates seamlessly with +more powerful event-driven handling via System.Reactive +or the more lightweight RxFree. +Subscribing to events with either of those packages is trivial:

+ +
IDisposable subscription;
+
+// constructor may use DI to get the dependency
+public CustomerViewModel(IMessageBus bus)
+{
+    subscription = bus.Observe<ItemShipped>().Subscribe(OnItemShipped);
+}
+
+void OnItemShipped(ItemShipped e) => // Refresh item status
+
+public void Dispose() => subscription.Dispose();
+
+ +

Commands

+ +

Commands can also be any type, and C# records make for concise definitions:

+ +
record CancelOrder(string OrderId) : IAsyncCommand;
+
+ +

Unlike events, command messages need to signal the invocation style they require +for execution:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScenarioInterfaceInvocation
void synchronous commandICommandIMessageBus.Execute(command)
value-returning synchronous commandICommand<TResult>var result = await IMessageBus.Execute(command)
void asynchronous commandIAsyncCommandawait IMessageBus.ExecuteAsync(command)
value-returning asynchronous commandIAsyncCommand<TResult>var result = await IMessageBus.ExecuteAsync(command)
+ +

The above command can be executed using the following code:

+ +
// perhaps a method invoked when a user 
+// clicks/taps a Cancel button next to an order
+async Task OnCancel(string orderId)
+{
+    await bus.ExecuteAsync(new CancelOrder(orderId), CancellationToken.None);
+    // refresh UI for new state.
+}
+
+ +

An example of a synchronous command could be:

+ +
// Command declaration
+record SignOut() : ICommand;
+
+// Command invocation
+void OnSignOut() => bus.Execute(new SignOut());
+
+// or alternatively, for void commands that have no additional data:
+void OnSignOut() => bus.Execute<SignOut>();
+
+ +

The marker interfaces on the command messages drive the compiler to only allow +the right invocation style on the message bus, as defined by the command author:

+ +
public interface IMessageBus
+{
+    // sync void
+    void Execute(ICommand command);
+    // sync value-returning
+    TResult Execute<TResult>(ICommand<TResult> command);
+    // async void
+    Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation);
+    // async value-returning
+    Task<TResult> ExecuteAsync<TResult>(IAsyncCommand<TResult> command, CancellationToken cancellation);
+}
+
+ +

For example, to create a value-returning async command that retrieves some +value, you would have:

+ +
record FindDocuments(string Filter) : IAsyncCommand<IEnumerable<string>>;
+
+class FindDocumentsHandler : IAsyncCommandHandler<FindDocument, IEnumerable<string>>
+{
+    public bool CanExecute(FindDocument command) => !string.IsNullOrEmpty(command.Filter);
+    
+    public Task<IEnumerable<string>> ExecuteAsync(FindDocument command, CancellationToken cancellation)
+        => // evaluate command.Filter across all documents and return matches
+}
+
+ +

In order to execute such command, the only execute method the compiler will allow +is:

+ +
IEnumerable<string> files = await bus.ExecuteAsync(new FindDocuments("*.json"));
+
+ +

If the consumer tries to use Execute, the compiler will complain that the +command does not implement ICommand<TResult>, which is the synchronous version +of the marker interface.

+ +

While these marker interfaces on the command messages might seem unnecessary, +they are actually quite important. They solve a key problem that execution +abstractions face: whether a command execution is synchronous or asynchronous +(as well as void or value-returning) should not be abstracted away since +otherwise you can end up in two common anti-patterns (i.e. async guidelines for ASP.NET), +known as sync over async and +async over sync.

+ +

Likewise, mistakes cannot be made when implementing the handler, since the +handler interfaces define constraints on what the commands must implement:

+ +
// sync
+public interface ICommandHandler<in TCommand> : ... where TCommand : ICommand;
+public interface ICommandHandler<in TCommand, out TResult> : ... where TCommand : ICommand<TResult>;
+
+// async
+public interface IAsyncCommandHandler<in TCommand> : ... where TCommand : IAsyncCommand;
+public interface IAsyncCommandHandler<in TCommand, TResult> : ... where TCommand : IAsyncCommand<TResult>
+
+ +

This design choice also makes it impossible to end up executing a command +implementation improperly.

+ +

In addition to execution, the IMessageBus also provides a mechanism to determine +if a command has a registered handler at all via the CanHandle<T> method as well +as a validation mechanism via CanExecute<T>, as shown above in the FindDocumentsHandler example.

+ +

Commands can notify new events, and event observers/subscribers can in turn +execute commands.

+ +

Analyzers and Code Fixes

+ +

Beyond the compiler complaining, Merq also provides a set of analyzers and +code fixes to learn the patterns and avoid common mistakes. For example, if you +created a simple record to use as a command, such as:

+ +
public record Echo(string Message);
+
+ +

And then tried to implement a command handler for it:

+ +
public class EchoHandler : ICommandHandler<Echo>
+{
+}
+
+ +

the compiler would immediately complain about various contraints and interfaces +that aren’t satisfied due to the requirements on the Echo type itself. For +a seasoned Merq developer, this is a no-brainer, but for new developers, +it can be a bit puzzling:

+ +

compiler warnings screenshot

+ +

A code fix is provided to automatically implement the required interfaces +in this case:

+ +

code fix to implement ICommand screenshot

+ +

Likewise, if a consumer attempted to invoke the above Echo command asynchronously +(known as the async over sync anti-pattern), +they would get a somewhat unintuitive compiler error:

+ +

error executing sync command as async

+ +

But the second error is more helpful, since it points to the actual problem, +and a code fix can be applied to resolve it:

+ +

code fix for executing sync command as async

+ +

The same analyzers and code fixes are provided for the opposite anti-pattern, +known as sync over async, +where a synchronous command is executed asynchronously.

+ + + +

Message Bus

+ +

The default implementation lives in a separate package Merq.Core +so that application components can take a dependency on just the interfaces.

+ +

Version +Downloads

+ + +

The default implementation of the message bus interface IMessageBus has +no external dependencies and can be instantiated via the MessageBus constructor +directly.

+ +

The bus locates command handlers and event producers via the passed-in +IServiceProvider instance in the constructor:

+ +
var bus = new MessageBus(serviceProvider);
+
+// execute a command
+bus.Execute(new MyCommand());
+
+// observe an event from the bus
+bus.Observe<MyEvent>().Subscribe(e => Console.WriteLine(e.Message));
+
+ + + +

When using dependency injection for .NET, +the Merq.DependencyInjection package +provides a simple mechanism for registering the message bus:

+ + +
var builder = WebApplication.CreateBuilder(args);
+...
+builder.Services.AddMessageBus();
+
+ +

All command handlers and event producers need to be registered with the +services collection as usual, using the main interface for the component, +such as ICommandHandler<T> and IObservable<TEvent>.

+ +

To drastically simplify registration of handlers and producers, we +recommend the Devlooped.Extensions.DependencyInjection.Attributed. +package, which provides a simple attribute-based mechanism for automatically +emitting at compile-time the required service registrations for all types +marked with the provided [Service] attribute, which also allows setting the +component lifetime, such as [Service(ServiceLifetime.Transient)].

+ +

This allows to simply mark all command handlers and event producers as +[Service] and then register them all with a single line of code:

+ +
builder.Services.AddServices();
+
+ +

Telemetry and Monitoring

+ +

The core implementation of the IMessageBus is instrumented with ActivitySource and +Metric, providing out of the box support for Open Telemetry-based monitoring, as well +as via dotnet trace +and dotnet counters.

+ +

To export telemetry using Open Telemetry, +for example:

+ +
using var tracer = Sdk
+    .CreateTracerProviderBuilder()
+    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ConsoleApp"))
+    .AddSource(source.Name)
+    .AddSource("Merq")
+    .AddConsoleExporter()
+    .AddZipkinExporter()
+    .AddAzureMonitorTraceExporter(o => o.ConnectionString = config["AppInsights"])
+    .Build();
+
+ +

Collecting traces via dotnet-trace:

+ +
dotnet trace collect --name [PROCESS_NAME] --providers="Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]Merq,System.Diagnostics.Metrics:::Metrics=Merq"
+
+ +

Monitoring metrics via dotnet-counters:

+ +
dotnet counters monitor --process-id [PROCESS_ID] --counters Merq
+
+ +

Example rendering from the included sample console app:

+ +

dotnet-counters screenshot

+ +

Duck Typing Support

+ + +

Being able to loosely couple both events (and their consumers) and command execution (from their +command handler implementations) is a key feature of Merq. To take this decoupling to the extreme, +Merq allows a similar capability as allowed by the TypeScript/JavaScript in VSCode: you can just +copy/paste an event/command definition as source into your assembly, and perform the regular +operations with it (like Observe an event and Execute a command), in a “duck typing” manner.

+ +

As long as the types’ full name match, the conversion will happen automatically. Since this +functionality isn’t required in many scenarios, and since there are a myriad ways to implement +such an object mapping functionality, the Merq.Core package only provides the hooks to enable +this, but does not provide any built-in implementation for it. In other words, no duck typing +is performed by default.

+ +

The Merq.AutoMapper package provides one such +implementation, based on the excelent AutoMapper library. It can be +registered with the DI container as follows:

+ +
builder.Services.AddMessageBus<AutoMapperMessageBus>();
+// register all services, including handlers and producers
+builder.Services.AddServices();
+
+ + + + + +

Dogfooding

+ +

CI Version +Build

+ +

We also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced.

+ +

The CI feed is https://pkg.kzu.dev/index.json.

+ +

The versioning scheme for packages is:

+ + + + + +

Sponsors

+ + +

Clarius Org +Kirill Osenkov +MFB Technologies, Inc. +Stephen Shaw +Torutek +DRIVE.NET, Inc. +Daniel Gnägi +Ashley Medway +Keith Pickford +Thomas Bolon +Kori Francis +Toni Wenzel +Giorgi Dalakishvili +Mike James +Dan Siegel +Reuben Swartz +Jacob Foshee + +Norman Mackay +Certify The Web +Ix Technologies B.V. +David JENNI +Jonathan +Oleg Kyrylchuk +Charley Wu +Jakob Tikjøb Andersen +Seann Alexander +Tino Hager +Mark Seemann +Angelo Belchior +Ken Bonny +Simon Cropp +agileworks-eu + +Vezel

+ + + +

Sponsor this project

+ +

Learn more about GitHub Sponsors

+ + + +
+
+ + + + + + + diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..83969dc --- /dev/null +++ b/license.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) Daniel Cazzulino and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..16f58cb --- /dev/null +++ b/readme.md @@ -0,0 +1,427 @@ +![Icon](https://raw.github.com/devlooped/Merq/main/assets/img/32.png) Merq +================ + +[![Version](https://img.shields.io/nuget/vpre/Merq.svg?color=royalblue)](https://www.nuget.org/packages/Merq) +[![Downloads](https://img.shields.io/nuget/dt/Merq.svg?color=green)](https://www.nuget.org/packages/Merq) +[![License](https://img.shields.io/github/license/devlooped/Merq.svg?color=blue)](https://github.com/devlooped/Merq/blob/main/license.txt) + + +> **Mercury:** messenger of the Roman gods + +> *Mercury* > *Merq-ry* > **Merq** + + +**Merq** brings the [Message Bus](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff647328(v=pandp.10)) pattern together with +a [command-oriented interface](https://www.martinfowler.com/bliki/CommandOrientedInterface.html) for an +extensible and decoupled in-process application architecture. + +These patterns are well established in microservices and service oriented +architectures, but their benefits can be applied to apps too, especially +extensible ones where multiple teams can contribute extensions which +are composed at run-time. + +The resulting improved decoupling between components makes it easier to evolve +them independently, while improving discoverability of available commands and +events. You can see this approach applied in the real world in +[VSCode commands](https://code.visualstudio.com/api/extension-guides/command) +and various events such as [window events](https://code.visualstudio.com/api/references/vscode-api#window). +Clearly, in the case of VSCode, everything is in-process, but the benefits of +a clean and predictable API are pretty obvious. + +*Merq* provides the same capabilities for .NET apps. + +## Events + +Events can be any type, there is no restriction or interfaces you must implement. +Nowadays, [C# record types](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records) +are a perfect fit for event data types. An example event could be a one-liner such as: + +```csharp +public record ItemShipped(string Id, DateTimeOffset Date); +``` + +The events-based API surface on the message bus is simple enough: + +```csharp +public interface IMessageBus +{ + void Notify(TEvent e); + IObservable Observe(); +} +``` + +By relying on `IObservable`, *Merq* integrates seamlessly with +more powerful event-driven handling via [System.Reactive](http://nuget.org/packages/system.reactive) +or the more lightweight [RxFree](https://www.nuget.org/packages/RxFree). +Subscribing to events with either of those packages is trivial: + +```csharp +IDisposable subscription; + +// constructor may use DI to get the dependency +public CustomerViewModel(IMessageBus bus) +{ + subscription = bus.Observe().Subscribe(OnItemShipped); +} + +void OnItemShipped(ItemShipped e) => // Refresh item status + +public void Dispose() => subscription.Dispose(); +``` + + +## Commands + +Commands can also be any type, and C# records make for concise definitions: + +```csharp +record CancelOrder(string OrderId) : IAsyncCommand; +``` + +Unlike events, command messages need to signal the invocation style they require +for execution: + +| Scenario | Interface | Invocation | +| --- | --- | --- | +| void synchronous command | `ICommand` | `IMessageBus.Execute(command)` | +| value-returning synchronous command | `ICommand` | `var result = await IMessageBus.Execute(command)` | +| void asynchronous command | `IAsyncCommand` | `await IMessageBus.ExecuteAsync(command)` | +| value-returning asynchronous command | `IAsyncCommand` | `var result = await IMessageBus.ExecuteAsync(command)` | + +The above command can be executed using the following code: + +```csharp +// perhaps a method invoked when a user +// clicks/taps a Cancel button next to an order +async Task OnCancel(string orderId) +{ + await bus.ExecuteAsync(new CancelOrder(orderId), CancellationToken.None); + // refresh UI for new state. +} +``` + +An example of a synchronous command could be: + +```csharp +// Command declaration +record SignOut() : ICommand; + +// Command invocation +void OnSignOut() => bus.Execute(new SignOut()); + +// or alternatively, for void commands that have no additional data: +void OnSignOut() => bus.Execute(); +``` + +The marker interfaces on the command messages drive the compiler to only allow +the right invocation style on the message bus, as defined by the command author: + +```csharp +public interface IMessageBus +{ + // sync void + void Execute(ICommand command); + // sync value-returning + TResult Execute(ICommand command); + // async void + Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation); + // async value-returning + Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation); +} +``` + +For example, to create a value-returning async command that retrieves some +value, you would have: + +```csharp +record FindDocuments(string Filter) : IAsyncCommand>; + +class FindDocumentsHandler : IAsyncCommandHandler> +{ + public bool CanExecute(FindDocument command) => !string.IsNullOrEmpty(command.Filter); + + public Task> ExecuteAsync(FindDocument command, CancellationToken cancellation) + => // evaluate command.Filter across all documents and return matches +} +``` + +In order to execute such command, the only execute method the compiler will allow +is: + +```csharp +IEnumerable files = await bus.ExecuteAsync(new FindDocuments("*.json")); +``` + +If the consumer tries to use `Execute`, the compiler will complain that the +command does not implement `ICommand`, which is the synchronous version +of the marker interface. + +While these marker interfaces on the command messages might seem unnecessary, +they are actually quite important. They solve a key problem that execution +abstractions face: whether a command execution is synchronous or asynchronous +(as well as void or value-returning) should *not* be abstracted away since +otherwise you can end up in two common anti-patterns (i.e. [async guidelines for ASP.NET](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md)), +known as [sync over async](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/) and +[async over sync](https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/). + +Likewise, mistakes cannot be made when implementing the handler, since the +handler interfaces define constraints on what the commands must implement: + +```csharp +// sync +public interface ICommandHandler : ... where TCommand : ICommand; +public interface ICommandHandler : ... where TCommand : ICommand; + +// async +public interface IAsyncCommandHandler : ... where TCommand : IAsyncCommand; +public interface IAsyncCommandHandler : ... where TCommand : IAsyncCommand +``` + +This design choice also makes it impossible to end up executing a command +implementation improperly. + +In addition to execution, the `IMessageBus` also provides a mechanism to determine +if a command has a registered handler at all via the `CanHandle` method as well +as a validation mechanism via `CanExecute`, as shown above in the `FindDocumentsHandler` example. + +Commands can notify new events, and event observers/subscribers can in turn +execute commands. + +## Analyzers and Code Fixes + +Beyond the compiler complaining, *Merq* also provides a set of analyzers and +code fixes to learn the patterns and avoid common mistakes. For example, if you +created a simple record to use as a command, such as: + +```csharp +public record Echo(string Message); +``` + +And then tried to implement a command handler for it: + +```csharp +public class EchoHandler : ICommandHandler +{ +} +``` + +the compiler would immediately complain about various contraints and interfaces +that aren't satisfied due to the requirements on the `Echo` type itself. For +a seasoned *Merq* developer, this is a no-brainer, but for new developers, +it can be a bit puzzling: + +![compiler warnings screenshot](https://github.com/devlooped/Merq/blob/main/assets/img/command-interfaces.png?raw=true) + +A code fix is provided to automatically implement the required interfaces +in this case: + +![code fix to implement ICommand screenshot](https://github.com/devlooped/Merq/blob/main/assets/img/implement-icommand.png?raw=true) + +Likewise, if a consumer attempted to invoke the above `Echo` command asynchronously +(known as the [async over sync anti-pattern](https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/)), +they would get a somewhat unintuitive compiler error: + +![error executing sync command as async](https://github.com/devlooped/Merq/blob/main/assets/img/async-sync-command.png?raw=true) + +But the second error is more helpful, since it points to the actual problem, +and a code fix can be applied to resolve it: + +![code fix for executing sync command as async](https://github.com/devlooped/Merq/blob/main/assets/img/async-sync-command-fix.png?raw=true) + +The same analyzers and code fixes are provided for the opposite anti-pattern, +known as [sync over async](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/), +where a synchronous command is executed asynchronously. + + + +## Message Bus + +The default implementation lives in a separate package [Merq.Core](https://www.nuget.org/packages/Merq.Core) +so that application components can take a dependency on just the interfaces. + +[![Version](https://img.shields.io/nuget/vpre/Merq.Core.svg?color=royalblue)](https://www.nuget.org/packages/Merq.Core) +[![Downloads](https://img.shields.io/nuget/dt/Merq.Core.svg?color=green)](https://www.nuget.org/packages/Merq.Core) + + +The default implementation of the message bus interface `IMessageBus` has +no external dependencies and can be instantiated via the `MessageBus` constructor +directly. + +The bus locates command handlers and event producers via the passed-in +`IServiceProvider` instance in the constructor: + +```csharp +var bus = new MessageBus(serviceProvider); + +// execute a command +bus.Execute(new MyCommand()); + +// observe an event from the bus +bus.Observe().Subscribe(e => Console.WriteLine(e.Message)); +``` + + + + +When using [dependency injection for .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection), +the [Merq.DependencyInjection](https://www.nuget.org/packages/Merq.DependencyInjection) package +provides a simple mechanism for registering the message bus: + + +```csharp +var builder = WebApplication.CreateBuilder(args); +... +builder.Services.AddMessageBus(); +``` + +All command handlers and event producers need to be registered with the +services collection as usual, using the main interface for the component, +such as `ICommandHandler` and `IObservable`. + +To drastically simplify registration of handlers and producers, we +recommend the [Devlooped.Extensions.DependencyInjection.Attributed](https://www.nuget.org/packages/Devlooped.Extensions.DependencyInjection.Attributed/). +package, which provides a simple attribute-based mechanism for automatically +emitting at compile-time the required service registrations for all types +marked with the provided `[Service]` attribute, which also allows setting the +component lifetime, such as `[Service(ServiceLifetime.Transient)]`. + +This allows to simply mark all command handlers and event producers as +`[Service]` and then register them all with a single line of code: + +```csharp +builder.Services.AddServices(); +``` + +### Telemetry and Monitoring + +The core implementation of the `IMessageBus` is instrumented with `ActivitySource` and +`Metric`, providing out of the box support for [Open Telemetry](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs)-based monitoring, as well +as via [dotnet trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) +and [dotnet counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters). + +To export telemetry using [Open Telemetry](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs), +for example: + +```csharp +using var tracer = Sdk + .CreateTracerProviderBuilder() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ConsoleApp")) + .AddSource(source.Name) + .AddSource("Merq") + .AddConsoleExporter() + .AddZipkinExporter() + .AddAzureMonitorTraceExporter(o => o.ConnectionString = config["AppInsights"]) + .Build(); +``` + +Collecting traces via [dotnet-trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace): + +```shell +dotnet trace collect --name [PROCESS_NAME] --providers="Microsoft-Diagnostics-DiagnosticSource:::FilterAndPayloadSpecs=[AS]Merq,System.Diagnostics.Metrics:::Metrics=Merq" +``` + +Monitoring metrics via [dotnet-counters](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters): + +```shell +dotnet counters monitor --process-id [PROCESS_ID] --counters Merq +``` + +Example rendering from the included sample console app: + +![dotnet-counters screenshot](https://github.com/devlooped/Merq/blob/main/assets/img/dotnet-counters.png) + +## Duck Typing Support + + +Being able to loosely couple both events (and their consumers) and command execution (from their +command handler implementations) is a key feature of Merq. To take this decoupling to the extreme, +Merq allows a similar capability as allowed by the TypeScript/JavaScript in VSCode: you can just +copy/paste an event/command definition as *source* into your assembly, and perform the regular +operations with it (like `Observe` an event and `Execute` a command), in a "duck typing" manner. + +As long as the types' full name match, the conversion will happen automatically. Since this +functionality isn't required in many scenarios, and since there are a myriad ways to implement +such an object mapping functionality, the `Merq.Core` package only provides the hooks to enable +this, but does not provide any built-in implementation for it. In other words, no duck typing +is performed by default. + +The [Merq.AutoMapper](https://www.nuget.org/packages/Merq.AutoMapper) package provides one such +implementation, based on the excelent [AutoMapper](https://automapper.org/) library. It can be +registered with the DI container as follows: + +```csharp +builder.Services.AddMessageBus(); +// register all services, including handlers and producers +builder.Services.AddServices(); +``` + + + + + +# Dogfooding + +[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.dev/vpre/Devlooped.Merq/main&label=nuget.ci&color=brightgreen)](https://pkg.kzu.dev/index.json) +[![Build](https://github.com/devlooped/Merq/workflows/build/badge.svg?branch=main)](https://github.com/devlooped/Merq/actions) + +We also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. + +The CI feed is `https://pkg.kzu.dev/index.json`. + +The versioning scheme for packages is: + +- PR builds: *42.42.42-pr*`[NUMBER]` +- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]` + + + +# Sponsors + + +[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png "Clarius Org")](https://github.com/clarius) +[![Kirill Osenkov](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KirillOsenkov.png "Kirill Osenkov")](https://github.com/KirillOsenkov) +[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) +[![Stephen Shaw](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/decriptor.png "Stephen Shaw")](https://github.com/decriptor) +[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png "Torutek")](https://github.com/torutek-gh) +[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png "DRIVE.NET, Inc.")](https://github.com/drivenet) +[![Daniel Gnägi](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dgnaegi.png "Daniel Gnägi")](https://github.com/dgnaegi) +[![Ashley Medway](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/AshleyMedway.png "Ashley Medway")](https://github.com/AshleyMedway) +[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png "Keith Pickford")](https://github.com/Keflon) +[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png "Thomas Bolon")](https://github.com/tbolon) +[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png "Kori Francis")](https://github.com/kfrancis) +[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png "Toni Wenzel")](https://github.com/twenzel) +[![Giorgi Dalakishvili](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Giorgi.png "Giorgi Dalakishvili")](https://github.com/Giorgi) +[![Mike James](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MikeCodesDotNET.png "Mike James")](https://github.com/MikeCodesDotNET) +[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png "Dan Siegel")](https://github.com/dansiegel) +[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png "Reuben Swartz")](https://github.com/rbnswartz) +[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png "Jacob Foshee")](https://github.com/jfoshee) +[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png "")](https://github.com/Mrxx99) +[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png "Eric Johnson")](https://github.com/eajhnsn1) +[![Norman Mackay](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/mackayn.png "Norman Mackay")](https://github.com/mackayn) +[![Certify The Web](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/certifytheweb.png "Certify The Web")](https://github.com/certifytheweb) +[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png "Ix Technologies B.V.")](https://github.com/IxTechnologies) +[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png "David JENNI")](https://github.com/davidjenni) +[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png "Jonathan ")](https://github.com/Jonathan-Hickey) +[![Oleg Kyrylchuk](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/okyrylchuk.png "Oleg Kyrylchuk")](https://github.com/okyrylchuk) +[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png "Charley Wu")](https://github.com/akunzai) +[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png "Jakob Tikjøb Andersen")](https://github.com/jakobt) +[![Seann Alexander](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/seanalexander.png "Seann Alexander")](https://github.com/seanalexander) +[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png "Tino Hager")](https://github.com/tinohager) +[![Mark Seemann](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ploeh.png "Mark Seemann")](https://github.com/ploeh) +[![Angelo Belchior](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/angelobelchior.png "Angelo Belchior")](https://github.com/angelobelchior) +[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png "Ken Bonny")](https://github.com/KenBonny) +[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png "Simon Cropp")](https://github.com/SimonCropp) +[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png "agileworks-eu")](https://github.com/agileworks-eu) +[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png "")](https://github.com/sorahex) +[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png "Zheyu Shen")](https://github.com/arsdragonfly) +[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png "Vezel")](https://github.com/vezel-dev) + + + + +[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png "Sponsor this project")](https://github.com/sponsors/devlooped) +  + +[Learn more about GitHub Sponsors](https://github.com/sponsors) + +