[{"data":1,"prerenderedAt":1102},["ShallowReactive",2],{"blog-post-case-refactoring-actions-pattern":3},{"id":4,"title":5,"body":6,"created":1093,"description":1094,"extension":1095,"isVisible":106,"meta":1096,"navigation":106,"path":1097,"seo":1098,"stem":1099,"tags":1100,"__hash__":1101},"blog\u002Fblog\u002Fcase-refactoring-actions-pattern.md","Refactoring actions using patterns",{"type":7,"value":8,"toc":1083},"minimark",[9,14,18,21,34,38,41,278,281,284,288,293,296,328,331,401,405,408,504,512,747,750,753,757,765,768,1048,1052,1055,1079],[10,11,13],"h2",{"id":12},"preface","Preface",[15,16,17],"p",{},"At the company I was working there was a specific application and \"mechanism\" for\nperforming specific decisions \u002F actions. These actions had several conditions in order to\nbe invoked and it was possible that an action had several smaller ones.",[15,19,20],{},"The tech team was having before issues with the code related with this feature. They did the\nfollowing refactoring:",[22,23,24,28,31],"ul",{},[25,26,27],"li",{},"Moved all actions into the database",[25,29,30],{},"Created a UI tool for administrating these action",[25,32,33],{},"An action was possible to be assigned to a \"group\" of actions",[10,35,37],{"id":36},"problem","Problem",[15,39,40],{},"Adapting and refactoring this solution had a great impact in the database and\nall actions. A single renaming \u002F refactoring on the database schema needed\nto update all queries stored in the tables. This refactoring took me over 3 days to find and\nadapt all queries stored.",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-sql shiki shiki-themes github-dark","action\n-------------------\nid | action_name  |\n---+--------------+\n 1 | open network |\n---+--------------+\n\naction_sql\n---+------------------------------------------------+\nid | acton_sql                                      |\n---+------------------------------------------------+\n 1 | UPDATE network SET enabled = 1 WHERE ID = :1   |\n 2 | UPDATE operator SET enabled = 1 WHERE ID = :2  |\n---+------------------------------------------------+\n\naction_action_sql\n---+-----------+---------------+\nid | action_id | action_sql_id |\n---+-----------+---------------+\n 1 | 1         | 1             |\n 2 | 1         | 2             |\n---+-----------+---------------+\n","sql","",[49,50,51,60,67,74,80,96,101,108,114,120,126,131,173,208,213,218,224,230,236,241,258,273],"code",{"__ignoreMap":47},[52,53,56],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"snl16","action\n",[52,61,63],{"class":54,"line":62},2,[52,64,66],{"class":65},"sAwPA","-------------------\n",[52,68,70],{"class":54,"line":69},3,[52,71,73],{"class":72},"s95oV","id | action_name  |\n",[52,75,77],{"class":54,"line":76},4,[52,78,79],{"class":65},"---+--------------+\n",[52,81,83,87,90,93],{"class":54,"line":82},5,[52,84,86],{"class":85},"sDLfK"," 1",[52,88,89],{"class":72}," | ",[52,91,92],{"class":58},"open",[52,94,95],{"class":72}," network |\n",[52,97,99],{"class":54,"line":98},6,[52,100,79],{"class":65},[52,102,104],{"class":54,"line":103},7,[52,105,107],{"emptyLinePlaceholder":106},true,"\n",[52,109,111],{"class":54,"line":110},8,[52,112,113],{"class":72},"action_sql\n",[52,115,117],{"class":54,"line":116},9,[52,118,119],{"class":65},"---+------------------------------------------------+\n",[52,121,123],{"class":54,"line":122},10,[52,124,125],{"class":72},"id | acton_sql                                      |\n",[52,127,129],{"class":54,"line":128},11,[52,130,119],{"class":65},[52,132,134,136,138,141,144,147,150,153,155,158,161,164,167,170],{"class":54,"line":133},12,[52,135,86],{"class":85},[52,137,89],{"class":72},[52,139,140],{"class":58},"UPDATE",[52,142,143],{"class":72}," network ",[52,145,146],{"class":58},"SET",[52,148,149],{"class":58}," enabled",[52,151,152],{"class":58}," =",[52,154,86],{"class":85},[52,156,157],{"class":58}," WHERE",[52,159,160],{"class":72}," ID ",[52,162,163],{"class":58},"=",[52,165,166],{"class":72}," :",[52,168,169],{"class":85},"1",[52,171,172],{"class":72},"   |\n",[52,174,176,179,181,183,186,188,190,192,194,196,198,200,202,205],{"class":54,"line":175},13,[52,177,178],{"class":85}," 2",[52,180,89],{"class":72},[52,182,140],{"class":58},[52,184,185],{"class":72}," operator ",[52,187,146],{"class":58},[52,189,149],{"class":58},[52,191,152],{"class":58},[52,193,86],{"class":85},[52,195,157],{"class":58},[52,197,160],{"class":72},[52,199,163],{"class":58},[52,201,166],{"class":72},[52,203,204],{"class":85},"2",[52,206,207],{"class":72},"  |\n",[52,209,211],{"class":54,"line":210},14,[52,212,119],{"class":65},[52,214,216],{"class":54,"line":215},15,[52,217,107],{"emptyLinePlaceholder":106},[52,219,221],{"class":54,"line":220},16,[52,222,223],{"class":72},"action_action_sql\n",[52,225,227],{"class":54,"line":226},17,[52,228,229],{"class":65},"---+-----------+---------------+\n",[52,231,233],{"class":54,"line":232},18,[52,234,235],{"class":72},"id | action_id | action_sql_id |\n",[52,237,239],{"class":54,"line":238},19,[52,240,229],{"class":65},[52,242,244,246,248,250,253,255],{"class":54,"line":243},20,[52,245,86],{"class":85},[52,247,89],{"class":72},[52,249,169],{"class":85},[52,251,252],{"class":72},"         | ",[52,254,169],{"class":85},[52,256,257],{"class":72},"             |\n",[52,259,261,263,265,267,269,271],{"class":54,"line":260},21,[52,262,178],{"class":85},[52,264,89],{"class":72},[52,266,169],{"class":85},[52,268,252],{"class":72},[52,270,204],{"class":85},[52,272,257],{"class":72},[52,274,276],{"class":54,"line":275},22,[52,277,229],{"class":65},[15,279,280],{},"The schema above seems easy at first sight, but after we add 50 actions with over 200 SQL\nqueries and many groups and inter-dependencies, it becomes very hard to maintain since all\nadaptations need to be done in the database.",[15,282,283],{},"At the end, developers built a UI in order to maintain this database schema. But still, changes\nin actions took days and maintainability was low.",[10,285,287],{"id":286},"refactoring","Refactoring",[289,290,292],"h3",{"id":291},"_1st-step-define-business-code","1st step - Define business code",[15,294,295],{},"I was definitely sure that I needed to find a solution that SQL queries should go back\nin the source code and services bean.  This way the code and backend will translate all\noperations in the database into real business operations. Instead of maintaining",[42,297,299],{"className":44,"code":298,"language":46,"meta":47,"style":47},"UPDATE operator SET enabled = false WHERE ID = 1;\n",[49,300,301],{"__ignoreMap":47},[52,302,303,305,307,309,311,313,316,319,321,323,325],{"class":54,"line":55},[52,304,140],{"class":58},[52,306,185],{"class":72},[52,308,146],{"class":58},[52,310,149],{"class":58},[52,312,152],{"class":58},[52,314,315],{"class":72}," false ",[52,317,318],{"class":58},"WHERE",[52,320,160],{"class":72},[52,322,163],{"class":58},[52,324,86],{"class":85},[52,326,327],{"class":72},";\n",[15,329,330],{},"We should wrap this query into reusable code:",[42,332,336],{"className":333,"code":334,"language":335,"meta":47,"style":47},"language-java shiki shiki-themes github-dark","public interface OperatorService {\n\n  \u002F**\n   * Disable a specific operator by ID\n   *\u002F\n  void  disableById(final Long id);\n}\n","java",[49,337,338,353,357,362,367,372,396],{"__ignoreMap":47},[52,339,340,343,346,350],{"class":54,"line":55},[52,341,342],{"class":58},"public",[52,344,345],{"class":58}," interface",[52,347,349],{"class":348},"svObZ"," OperatorService",[52,351,352],{"class":72}," {\n",[52,354,355],{"class":54,"line":62},[52,356,107],{"emptyLinePlaceholder":106},[52,358,359],{"class":54,"line":69},[52,360,361],{"class":65},"  \u002F**\n",[52,363,364],{"class":54,"line":76},[52,365,366],{"class":65},"   * Disable a specific operator by ID\n",[52,368,369],{"class":54,"line":82},[52,370,371],{"class":65},"   *\u002F\n",[52,373,374,377,380,383,386,389,393],{"class":54,"line":98},[52,375,376],{"class":58},"  void",[52,378,379],{"class":348},"  disableById",[52,381,382],{"class":72},"(",[52,384,385],{"class":58},"final",[52,387,388],{"class":72}," Long ",[52,390,392],{"class":391},"s9osk","id",[52,394,395],{"class":72},");\n",[52,397,398],{"class":54,"line":103},[52,399,400],{"class":72},"}\n",[289,402,404],{"id":403},"_2nd-step-the-actions-mechanism","2nd step - The actions mechanism",[15,406,407],{},"After understanding that actions could also include \"sub-actions\" inside them, I started\nthinking about the command pattern. This will allow me to write composite commands\nand organize code much better.",[42,409,411],{"className":333,"code":410,"language":335,"meta":47,"style":47},"public interface NetworkAction {\n\n    @NonNull\n    String getLabel();\n\n    boolean supports(@NonNull final Network network);\n\n    void execute(@NonNull final Network network);\n}\n",[49,412,413,424,428,436,447,451,476,480,500],{"__ignoreMap":47},[52,414,415,417,419,422],{"class":54,"line":55},[52,416,342],{"class":58},[52,418,345],{"class":58},[52,420,421],{"class":348}," NetworkAction",[52,423,352],{"class":72},[52,425,426],{"class":54,"line":62},[52,427,107],{"emptyLinePlaceholder":106},[52,429,430,433],{"class":54,"line":69},[52,431,432],{"class":72},"    @",[52,434,435],{"class":58},"NonNull\n",[52,437,438,441,444],{"class":54,"line":76},[52,439,440],{"class":72},"    String ",[52,442,443],{"class":348},"getLabel",[52,445,446],{"class":72},"();\n",[52,448,449],{"class":54,"line":82},[52,450,107],{"emptyLinePlaceholder":106},[52,452,453,456,459,462,465,468,471,474],{"class":54,"line":98},[52,454,455],{"class":58},"    boolean",[52,457,458],{"class":348}," supports",[52,460,461],{"class":72},"(@",[52,463,464],{"class":58},"NonNull",[52,466,467],{"class":58}," final",[52,469,470],{"class":72}," Network ",[52,472,473],{"class":391},"network",[52,475,395],{"class":72},[52,477,478],{"class":54,"line":103},[52,479,107],{"emptyLinePlaceholder":106},[52,481,482,485,488,490,492,494,496,498],{"class":54,"line":110},[52,483,484],{"class":58},"    void",[52,486,487],{"class":348}," execute",[52,489,461],{"class":72},[52,491,464],{"class":58},[52,493,467],{"class":58},[52,495,470],{"class":72},[52,497,473],{"class":391},[52,499,395],{"class":72},[52,501,502],{"class":54,"line":116},[52,503,400],{"class":72},[15,505,506,507,511],{},"The ",[508,509,510],"em",{},"supports"," method checks if the action is available for the network provided.\nA simple implementation of the interface abve would be the following:",[42,513,515],{"className":333,"code":514,"language":335,"meta":47,"style":47},"@Component\npublic class DisableNetworkAction implements NetworkAction {\n\n    private final NetworkService networkService;\n\n    @Override\n    public String getLabel() {\n        return \"Disable network\";\n    }\n\n\n    public DisableNetworkAction(final NetworkService networkService) {\n        this.networkService = networkService;\n    }\n\n    @Override\n    public boolean supports(final Network network) {\n        return !network.isDisabled();\n    }\n\n    @Override\n    public void execute(Network network) {\n        networkService.disableById(network.getId());\n    }\n\n}\n",[49,516,517,525,542,546,556,560,567,580,591,596,600,604,623,636,640,644,650,669,684,688,692,698,714,732,737,742],{"__ignoreMap":47},[52,518,519,522],{"class":54,"line":55},[52,520,521],{"class":72},"@",[52,523,524],{"class":58},"Component\n",[52,526,527,529,532,535,538,540],{"class":54,"line":62},[52,528,342],{"class":58},[52,530,531],{"class":58}," class",[52,533,534],{"class":348}," DisableNetworkAction",[52,536,537],{"class":58}," implements",[52,539,421],{"class":348},[52,541,352],{"class":72},[52,543,544],{"class":54,"line":69},[52,545,107],{"emptyLinePlaceholder":106},[52,547,548,551,553],{"class":54,"line":76},[52,549,550],{"class":58},"    private",[52,552,467],{"class":58},[52,554,555],{"class":72}," NetworkService networkService;\n",[52,557,558],{"class":54,"line":82},[52,559,107],{"emptyLinePlaceholder":106},[52,561,562,564],{"class":54,"line":98},[52,563,432],{"class":72},[52,565,566],{"class":58},"Override\n",[52,568,569,572,575,577],{"class":54,"line":103},[52,570,571],{"class":58},"    public",[52,573,574],{"class":72}," String ",[52,576,443],{"class":348},[52,578,579],{"class":72},"() {\n",[52,581,582,585,589],{"class":54,"line":110},[52,583,584],{"class":58},"        return",[52,586,588],{"class":587},"sU2Wk"," \"Disable network\"",[52,590,327],{"class":72},[52,592,593],{"class":54,"line":116},[52,594,595],{"class":72},"    }\n",[52,597,598],{"class":54,"line":122},[52,599,107],{"emptyLinePlaceholder":106},[52,601,602],{"class":54,"line":128},[52,603,107],{"emptyLinePlaceholder":106},[52,605,606,608,610,612,614,617,620],{"class":54,"line":133},[52,607,571],{"class":58},[52,609,534],{"class":348},[52,611,382],{"class":72},[52,613,385],{"class":58},[52,615,616],{"class":72}," NetworkService ",[52,618,619],{"class":391},"networkService",[52,621,622],{"class":72},") {\n",[52,624,625,628,631,633],{"class":54,"line":175},[52,626,627],{"class":85},"        this",[52,629,630],{"class":72},".networkService ",[52,632,163],{"class":58},[52,634,635],{"class":72}," networkService;\n",[52,637,638],{"class":54,"line":210},[52,639,595],{"class":72},[52,641,642],{"class":54,"line":215},[52,643,107],{"emptyLinePlaceholder":106},[52,645,646,648],{"class":54,"line":220},[52,647,432],{"class":72},[52,649,566],{"class":58},[52,651,652,654,657,659,661,663,665,667],{"class":54,"line":226},[52,653,571],{"class":58},[52,655,656],{"class":58}," boolean",[52,658,458],{"class":348},[52,660,382],{"class":72},[52,662,385],{"class":58},[52,664,470],{"class":72},[52,666,473],{"class":391},[52,668,622],{"class":72},[52,670,671,673,676,679,682],{"class":54,"line":232},[52,672,584],{"class":58},[52,674,675],{"class":58}," !",[52,677,678],{"class":72},"network.",[52,680,681],{"class":348},"isDisabled",[52,683,446],{"class":72},[52,685,686],{"class":54,"line":238},[52,687,595],{"class":72},[52,689,690],{"class":54,"line":243},[52,691,107],{"emptyLinePlaceholder":106},[52,693,694,696],{"class":54,"line":260},[52,695,432],{"class":72},[52,697,566],{"class":58},[52,699,700,702,705,707,710,712],{"class":54,"line":275},[52,701,571],{"class":58},[52,703,704],{"class":58}," void",[52,706,487],{"class":348},[52,708,709],{"class":72},"(Network ",[52,711,473],{"class":391},[52,713,622],{"class":72},[52,715,717,720,723,726,729],{"class":54,"line":716},23,[52,718,719],{"class":72},"        networkService.",[52,721,722],{"class":348},"disableById",[52,724,725],{"class":72},"(network.",[52,727,728],{"class":348},"getId",[52,730,731],{"class":72},"());\n",[52,733,735],{"class":54,"line":734},24,[52,736,595],{"class":72},[52,738,740],{"class":54,"line":739},25,[52,741,107],{"emptyLinePlaceholder":106},[52,743,745],{"class":54,"line":744},26,[52,746,400],{"class":72},[15,748,749],{},"This pattern used differs actually from the command pattern, since all actions\nare stateless. The good part is that this way I can use dependency injection\nand organize my beans. The bad part is that rollback cannot be implemented and\nwas removed.",[15,751,752],{},"The fact was that rollback was not even used anymore and all actions had a\na way to revert the previous one. This way I was able to convince business to\nremove this feature.",[289,754,756],{"id":755},"_3rd-step-conditions-builder","3rd step - conditions builder",[15,758,759,760,764],{},"After building complicated actions I noticed that the conditions were getting\ncomplicated with many sub conditions and over 5 lines of boolean expressions.\nIn order to make this more readable, I tried to utilize a ",[761,762,763],"strong",{},"builder"," for\nconditions to transform expressions into a domain-specific language.",[15,766,767],{},"After refactoring, a complicated action was looking like the example below:",[42,769,771],{"className":333,"code":770,"language":335,"meta":47,"style":47},"public class ShutdownNetworkAction implements NetworkAction {\n\n  private final SetStatusClosedNetworkAction setStatusClosedNetworkAction;\n  private final DisableNetworkAction disableNetworkAction;\n\n  @Autowired\n  public ShutdownNetworkAction(final SetStatusClosedNetworkAction setStatusClosedNetworkAction,\n                               final DisableNetworkAction disableNetworkAction) {\n    this.disableNetworkAction = disableNetworkAction;\n    this.setStatusClosedNetworkAction = setStatusClosedNetworkAction;\n  }\n\n  @Override\n  public String getLabel() {\n    return \"Shutdown network\";\n  }\n\n  @Override\n  public boolean supports(Network network) {\n    return ConditionBuilder.create(network)\n      .isValid()\n      .supportAll(disableNetworkAction, setStatusClosedNetworkAction)\n      .build();\n  }\n\n  @Override\n  public void execute(Network network) {\n    setStatusClosedNetworkAction.execute(network);\n    disableNetworkAction.execute(network);\n  }\n}\n",[49,772,773,788,792,802,811,815,823,843,856,869,881,886,890,896,906,916,920,924,930,944,957,968,978,987,991,995,1001,1016,1028,1038,1043],{"__ignoreMap":47},[52,774,775,777,779,782,784,786],{"class":54,"line":55},[52,776,342],{"class":58},[52,778,531],{"class":58},[52,780,781],{"class":348}," ShutdownNetworkAction",[52,783,537],{"class":58},[52,785,421],{"class":348},[52,787,352],{"class":72},[52,789,790],{"class":54,"line":62},[52,791,107],{"emptyLinePlaceholder":106},[52,793,794,797,799],{"class":54,"line":69},[52,795,796],{"class":58},"  private",[52,798,467],{"class":58},[52,800,801],{"class":72}," SetStatusClosedNetworkAction setStatusClosedNetworkAction;\n",[52,803,804,806,808],{"class":54,"line":76},[52,805,796],{"class":58},[52,807,467],{"class":58},[52,809,810],{"class":72}," DisableNetworkAction disableNetworkAction;\n",[52,812,813],{"class":54,"line":82},[52,814,107],{"emptyLinePlaceholder":106},[52,816,817,820],{"class":54,"line":98},[52,818,819],{"class":72},"  @",[52,821,822],{"class":58},"Autowired\n",[52,824,825,828,830,832,834,837,840],{"class":54,"line":103},[52,826,827],{"class":58},"  public",[52,829,781],{"class":348},[52,831,382],{"class":72},[52,833,385],{"class":58},[52,835,836],{"class":72}," SetStatusClosedNetworkAction ",[52,838,839],{"class":391},"setStatusClosedNetworkAction",[52,841,842],{"class":72},",\n",[52,844,845,848,851,854],{"class":54,"line":110},[52,846,847],{"class":58},"                               final",[52,849,850],{"class":72}," DisableNetworkAction ",[52,852,853],{"class":391},"disableNetworkAction",[52,855,622],{"class":72},[52,857,858,861,864,866],{"class":54,"line":116},[52,859,860],{"class":85},"    this",[52,862,863],{"class":72},".disableNetworkAction ",[52,865,163],{"class":58},[52,867,868],{"class":72}," disableNetworkAction;\n",[52,870,871,873,876,878],{"class":54,"line":122},[52,872,860],{"class":85},[52,874,875],{"class":72},".setStatusClosedNetworkAction ",[52,877,163],{"class":58},[52,879,880],{"class":72}," setStatusClosedNetworkAction;\n",[52,882,883],{"class":54,"line":128},[52,884,885],{"class":72},"  }\n",[52,887,888],{"class":54,"line":133},[52,889,107],{"emptyLinePlaceholder":106},[52,891,892,894],{"class":54,"line":175},[52,893,819],{"class":72},[52,895,566],{"class":58},[52,897,898,900,902,904],{"class":54,"line":210},[52,899,827],{"class":58},[52,901,574],{"class":72},[52,903,443],{"class":348},[52,905,579],{"class":72},[52,907,908,911,914],{"class":54,"line":215},[52,909,910],{"class":58},"    return",[52,912,913],{"class":587}," \"Shutdown network\"",[52,915,327],{"class":72},[52,917,918],{"class":54,"line":220},[52,919,885],{"class":72},[52,921,922],{"class":54,"line":226},[52,923,107],{"emptyLinePlaceholder":106},[52,925,926,928],{"class":54,"line":232},[52,927,819],{"class":72},[52,929,566],{"class":58},[52,931,932,934,936,938,940,942],{"class":54,"line":238},[52,933,827],{"class":58},[52,935,656],{"class":58},[52,937,458],{"class":348},[52,939,709],{"class":72},[52,941,473],{"class":391},[52,943,622],{"class":72},[52,945,946,948,951,954],{"class":54,"line":243},[52,947,910],{"class":58},[52,949,950],{"class":72}," ConditionBuilder.",[52,952,953],{"class":348},"create",[52,955,956],{"class":72},"(network)\n",[52,958,959,962,965],{"class":54,"line":260},[52,960,961],{"class":72},"      .",[52,963,964],{"class":348},"isValid",[52,966,967],{"class":72},"()\n",[52,969,970,972,975],{"class":54,"line":275},[52,971,961],{"class":72},[52,973,974],{"class":348},"supportAll",[52,976,977],{"class":72},"(disableNetworkAction, setStatusClosedNetworkAction)\n",[52,979,980,982,985],{"class":54,"line":716},[52,981,961],{"class":72},[52,983,984],{"class":348},"build",[52,986,446],{"class":72},[52,988,989],{"class":54,"line":734},[52,990,885],{"class":72},[52,992,993],{"class":54,"line":739},[52,994,107],{"emptyLinePlaceholder":106},[52,996,997,999],{"class":54,"line":744},[52,998,819],{"class":72},[52,1000,566],{"class":58},[52,1002,1004,1006,1008,1010,1012,1014],{"class":54,"line":1003},27,[52,1005,827],{"class":58},[52,1007,704],{"class":58},[52,1009,487],{"class":348},[52,1011,709],{"class":72},[52,1013,473],{"class":391},[52,1015,622],{"class":72},[52,1017,1019,1022,1025],{"class":54,"line":1018},28,[52,1020,1021],{"class":72},"    setStatusClosedNetworkAction.",[52,1023,1024],{"class":348},"execute",[52,1026,1027],{"class":72},"(network);\n",[52,1029,1031,1034,1036],{"class":54,"line":1030},29,[52,1032,1033],{"class":72},"    disableNetworkAction.",[52,1035,1024],{"class":348},[52,1037,1027],{"class":72},[52,1039,1041],{"class":54,"line":1040},30,[52,1042,885],{"class":72},[52,1044,1046],{"class":54,"line":1045},31,[52,1047,400],{"class":72},[10,1049,1051],{"id":1050},"summary","Summary",[15,1053,1054],{},"Here are few thoughts from what I learned from this refactoring",[22,1056,1057,1060,1063,1066,1073],{},[25,1058,1059],{},"Business code belongs in services and need to be reusable",[25,1061,1062],{},"No queries shall be stored in a database schema",[25,1064,1065],{},"If data are not modified by end-users, maybe it is not a good idea to\nstore them in database",[25,1067,1068,1069,1072],{},"A ",[508,1070,1071],{},"command pattern"," can help a lot when actions are composite and consist\nof smaller sub-actions",[25,1074,506,1075,1078],{},[508,1076,1077],{},"builder pattern"," can help you build your own domain-specific language",[1080,1081,1082],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":47,"searchDepth":62,"depth":62,"links":1084},[1085,1086,1087,1092],{"id":12,"depth":62,"text":13},{"id":36,"depth":62,"text":37},{"id":286,"depth":62,"text":287,"children":1088},[1089,1090,1091],{"id":291,"depth":69,"text":292},{"id":403,"depth":69,"text":404},{"id":755,"depth":69,"text":756},{"id":1050,"depth":62,"text":1051},1521498900,"This is a description 1","md",{},"\u002Fblog\u002Fcase-refactoring-actions-pattern",{"title":5,"description":1094},"blog\u002Fcase-refactoring-actions-pattern",[],"TRGkUfQxVsn6KDWzXSm7DRItPcRiXVFQ6VwqtqgMrMs",1786914024312]