<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tech | Jacob Aloysious</title><link>https://jacobaloysious.in/category/tech/</link><atom:link href="https://jacobaloysious.in/category/tech/index.xml" rel="self" type="application/rss+xml"/><description>Tech</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><image><url>https://jacobaloysious.in/images/icon_hu4591c05f594249c11c1e99a3a8f1f246_3759739_512x512_fill_lanczos_center_2.png</url><title>Tech</title><link>https://jacobaloysious.in/category/tech/</link></image><item><title>An AI-First Second Brain for Engineering Teams: My Talk at KLA</title><link>https://jacobaloysious.in/post/ai-first-second-brain-kla-conference/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/ai-first-second-brain-kla-conference/</guid><description>&lt;p>I had the opportunity to speak at &lt;strong>
&lt;a href="https://www.kla.com/" target="_blank" rel="noopener">KLA&lt;/a>&amp;lsquo;s Engineering Conference&lt;/strong>, an event attended by &lt;strong>800–1,000 engineers&lt;/strong>, about a problem that sits underneath much of our engineering work: remembering enough of the right context to make the next decision well.&lt;/p>
&lt;p>KLA serves the semiconductor industry with process-control, inspection, and metrology technologies. That made it a fitting setting for a conversation about preserving context across complex engineering work.&lt;/p>
&lt;p>My talk, &lt;strong>“AI-First Second Brain for Engineering Teams,”&lt;/strong> explored how AI can help preserve that context without turning every conversation, incident, or provisional idea into permanent truth.&lt;/p>
&lt;p>I had a &lt;strong>90-minute slot&lt;/strong>. The talk itself lasted &lt;strong>75 minutes&lt;/strong>, followed by &lt;strong>more than 30 minutes of Q&amp;amp;A&lt;/strong>, taking the session beyond its allotted time. There was a lot of enthusiasm for the topic, and it was encouraging to see that interest continue into the discussion.&lt;/p>
&lt;p>The message I wanted to leave with the audience was simple: AI can do much of the work of organizing and maintaining memory. People still need to decide what is safe to share, what deserves to be remembered, and when an old lesson applies to a new situation.&lt;/p>
&lt;p>&lt;img src="conference-stage.jpg" alt="Presenting AI-First Second Brain for Engineering Teams at KLA&amp;rsquo;s Engineering Conference">&lt;/p>
&lt;p>&lt;em>On stage at KLA&amp;rsquo;s NEOTERIX 2026 Engineering Conference in Chennai.&lt;/em>&lt;/p>
&lt;h2 id="the-work-before-the-work">The work before the work&lt;/h2>
&lt;p>Think about a difficult engineering issue. The logs are in one place. Someone else has the physical observation. The reason for an old design choice is buried in a discussion. A workaround exists in the memory of an engineer who happens to be unavailable.&lt;/p>
&lt;p>Before anyone can investigate properly, the team has to reconstruct the story.&lt;/p>
&lt;p>I described this in the talk as engineering archaeology. We are searching for what happened, who understood it, which assumptions mattered, and whether the previous solution is still relevant. The information may exist, but it is not yet a usable starting point.&lt;/p>
&lt;p>That reconstruction carries a &lt;strong>memory tax&lt;/strong>. Engineers pay it when debugging an unfamiliar failure. Managers pay it when recovering an unowned commitment. Leaders pay it when trying to understand whether a team has learned something reusable or simply closed another issue.&lt;/p>
&lt;p>The questions differ, but the dependency is similar: useful action waits for context. Adding more documents does not automatically reduce that delay. A larger collection can also make it harder to distinguish current guidance from an abandoned experiment.&lt;/p>
&lt;h2 id="what-i-mean-by-a-second-brain">What I mean by a second brain&lt;/h2>
&lt;p>For this talk, a second brain meant a maintained body of context that helps a team continue its work. It includes decisions and their rationale, incident history, runbooks, known limitations, and commitments that need follow-through.&lt;/p>
&lt;p>The useful distinction is between storing information and maintaining it. A stored note can remain available long after its assumptions have expired. Maintained knowledge has a visible source, an owner, a verification state, and a reason to revisit it. It can be corrected or retired.&lt;/p>
&lt;p>The recurring flow in the talk was:&lt;/p>
&lt;p>&lt;strong>Capture → Govern → Surface → Decide&lt;/strong>&lt;/p>
&lt;p>Capture brings in relevant observations. Governance determines what may persist and who can use it. Retrieval surfaces the right material with its supporting evidence. A person then makes the decision with enough context to understand the consequences.&lt;/p>
&lt;p>This also changes how I think about expert dependency. An experienced engineer&amp;rsquo;s judgment is valuable, but their availability should not be the only route to the team&amp;rsquo;s history. Shared memory can give another engineer the sources, reviewed guidance, and prior investigations needed to make progress.&lt;/p>
&lt;p>It does not reproduce the expert. It preserves a better starting position for whoever is doing the work next.&lt;/p>
&lt;h2 id="give-ai-the-maintenance-work">Give AI the maintenance work&lt;/h2>
&lt;p>A large part of the talk concerned the division of responsibility. AI is useful for extracting candidate decisions, linking related cases, drafting summaries, noticing stale guidance, and bringing open questions back for review.&lt;/p>
&lt;p>Those tasks consume attention, especially when context is scattered across messages, meetings, documents, and alerts. An assistant can prepare the material continuously, leaving people with smaller, more specific decisions to make.&lt;/p>
&lt;p>In the personal workflow I described, Google Chat is the interaction surface, MyClaw is the agent runtime, and human-readable Markdown in Google Drive holds durable memory. The runtime coordinates tools, policies, and scheduled work; the underlying model is one component of that system.&lt;/p>
&lt;p>The particular products matter less than the arrangement. People need somewhere to review and correct proposed memory. Durable records need to remain understandable outside a single chat session. Cases, decisions, and approved knowledge need different lifecycles.&lt;/p>
&lt;p>A useful record should explain where it came from, who owns it, when it was checked, and what would make it questionable again. That gives the next reader a way to assess it instead of accepting a confident summary at face value.&lt;/p>
&lt;h2 id="five-loops-that-keep-memory-useful">Five loops that keep memory useful&lt;/h2>
&lt;p>I organized the maintenance work into five recurring loops. Together, they turn a collection of notes into something a team can keep using.&lt;/p>
&lt;p>&lt;strong>1. Capture decisions for review.&lt;/strong> After an architecture discussion, the assistant can extract the decision, its rationale, and the condition under which it should be revisited. A person can store, edit, or skip the proposal. Recording the reason matters: without it, a future team may preserve a decision after the original constraint has disappeared.&lt;/p>
&lt;p>&lt;strong>2. Assist with incidents.&lt;/strong> During an investigation, the assistant can surface a relevant playbook, its source, its verification state, and recent changes. The engineer chooses the next action. The assistant can preserve the checkpoint so the next handoff does not restart the investigation from scratch.&lt;/p>
&lt;p>&lt;strong>3. Draft weekly updates.&lt;/strong> Actual activity can become a draft of delivered work, ongoing work, risks, and next steps. Human review remains part of publication. This is valuable because an update should reflect what happened, including uncertainty, rather than merely sound complete.&lt;/p>
&lt;p>&lt;strong>4. Check freshness.&lt;/strong> Guidance should return for review when time passes, a dependency changes, or an original assumption stops holding. AI can identify those triggers. An owner decides whether the record remains valid, needs correction, or should be archived.&lt;/p>
&lt;p>&lt;strong>5. Propose reusable knowledge.&lt;/strong> Several cases may suggest a pattern worth preserving. The assistant can prepare a narrow proposal with evidence and limitations. A named owner reviews it before it becomes guidance for others.&lt;/p>
&lt;p>These loops make maintenance a recurring responsibility. They also give the human reviewer a concrete question to answer at each step.&lt;/p>
&lt;p>&lt;img src="conference-speaker.jpg" alt="Speaking about governed engineering memory at KLA&amp;rsquo;s Engineering Conference">&lt;/p>
&lt;p>&lt;em>Discussing how AI can preserve context while keeping consequential decisions with people.&lt;/em>&lt;/p>
&lt;h2 id="a-resolved-case-is-not-automatically-reusable-knowledge">A resolved case is not automatically reusable knowledge&lt;/h2>
&lt;p>This was one of the most important distinctions in the talk. A fix can be correct for one incident and misleading when generalized.&lt;/p>
&lt;p>A case records what happened under particular conditions. Reusable knowledge makes a broader claim: under these conditions, this diagnostic or action is worth considering again. Moving between the two requires evidence, scope, limitations, and ownership.&lt;/p>
&lt;p>For example, a recovery procedure might have worked with one configuration. Before it becomes a runbook recommendation, someone needs to ask what actually explains the recovery, which prerequisites matter, and when the procedure should not be used.&lt;/p>
&lt;p>The same applies to AI-generated summaries. Fluent language can hide how narrow the original evidence was. Promotion should therefore be an explicit action, with a reviewer responsible for the claim that future engineers will inherit.&lt;/p>
&lt;p>Most cases can remain cases. Their history is still useful. Only selected lessons need to become shared guidance, and that guidance needs a path back to its supporting evidence.&lt;/p>
&lt;p>&lt;img src="cases-are-not-knowledge.png" alt="Cases remain traceable in case history; only narrow, verified learning enters the Team Brain after owner review">&lt;/p>
&lt;p>&lt;em>Many cases, few promotions: reusable guidance needs verified evidence, a source trail, and an owner.&lt;/em>&lt;/p>
&lt;h2 id="making-the-boundaries-concrete-with-a-field-issue-demo">Making the boundaries concrete with a field-issue demo&lt;/h2>
&lt;p>I tailored the demo to KLA’s semiconductor engineering context: how AI could support an end-to-end workflow from a field issue to a tested code fix, with human approval at the consequential steps. The scenario is fictional and illustrates the proposed approach. The presenter application is a deterministic simulation backed by local artifacts and tests. It does not operate production equipment, call a live model, or deploy changes to an external system.&lt;/p>
&lt;p>I see this as a practical example of &lt;strong>forward-deployed engineering&lt;/strong>: working from a specific customer use case and shaping the AI solution around that customer’s environment. Here, the problem is moving from a semiconductor field issue to an actionable engineering investigation. The design follows the evidence available at the tool, the restrictions on sharing it, and the engineer’s responsibility for the fix. That customer context determines where AI can help and where human judgment must remain.&lt;/p>
&lt;p>&lt;img src="kla-ai-automation-workflow-clean.png" alt="Demo workflow: an OnTool Agent prepares a sanitized context packet, a human approves sharing, and an Engineering Agent prepares a tested fix using the same approved Team Brain">&lt;/p>
&lt;p>&lt;em>The automation workflow presented at KLA: raw evidence stays local, only an approved packet crosses the boundary, and an engineer reviews and ships the proposed fix.&lt;/em>&lt;/p>
&lt;p>The scenario follows evidence from a tool-side investigation to an engineering diagnosis. An on-tool agent prepares a sanitized packet while restricted raw evidence stays local. The packet must be reviewed and approved before it crosses the boundary to the engineering side.&lt;/p>
&lt;p>One deliberate step is attempting the transfer too early. The system refuses it. That refusal makes the permission boundary visible: information being useful does not, by itself, authorize sharing it.&lt;/p>
&lt;p>On the engineering side, the approved packet is combined with shared knowledge, code, and tests. The example concerns a settle gate that checks position while motion is still unstable. The workflow prepares a diagnostic experiment and a proposed runtime and test change. The engineer retains responsibility for reviewing and shipping the change through the normal process.&lt;/p>
&lt;p>A second gate appears after the simulated resolution. The workflow refuses to promote the finding into reusable knowledge until an owner approves it. The later cold-start view shows what another engineer could inherit months afterward: a scoped diagnostic path, evidence, ownership, and review conditions.&lt;/p>
&lt;p>This is the behavior I wanted the demo to make tangible. Better context can shorten the route to an investigation, while explicit gates preserve responsibility for sharing, software changes, and future reuse.&lt;/p>
&lt;p>In a real implementation, those gates also require technical enforcement through credentials, isolation, network controls, validation, and audit records. A prompt alone cannot provide that boundary.&lt;/p>
&lt;h2 id="memory-about-people-needs-its-own-limits">Memory about people needs its own limits&lt;/h2>
&lt;p>Engineering context includes people, and that requires care. Remembering an agreed follow-up can help a team. Turning an impression into a lasting judgment about a colleague can cause harm.&lt;/p>
&lt;p>The manager workflow in the materials separates fragments into three outcomes: remember, keep uncertain, or reject. A professional commitment may be appropriate to retain. A conditional statement must keep its condition. A speculative personal judgment should not become durable memory.&lt;/p>
&lt;p>Consider the difference between “I can review this if the release work finishes” and “I will review this.” Removing the condition changes the commitment. An assistant that compresses the first statement into the second has created an inaccurate expectation, even if its summary reads naturally.&lt;/p>
&lt;p>That is why visibility and correction matter. People should be able to inspect the source of a remembered claim, correct it, and understand the boundary around its use. Useful continuity depends on restraint as much as recall.&lt;/p>
&lt;h2 id="start-with-one-decision-that-keeps-losing-context">Start with one decision that keeps losing context&lt;/h2>
&lt;p>My proposed starting point was a small, thirty-day pilot around one recurring problem. Choose a decision where the team repeatedly reconstructs history, then define what better continuity would look like.&lt;/p>
&lt;p>A recurring incident, an architecture decision log, or a handoff process could be a suitable starting place. Give it one owner, a clear boundary around what may be stored, and a measurable outcome.&lt;/p>
&lt;p>The build path in the talk moves through four stages: define the job and stop rules, wire the workflow and failure handling, ground retrieval and human review, then add ongoing ownership and operational checks.&lt;/p>
&lt;p>The tests should cover the memory contract as well as answer quality. Does the workflow reject unreviewed material? Does it preserve conditional language? Can a person correct a record? Does promotion require approval? Can the system show where its guidance came from?&lt;/p>
&lt;p>Measure whether the next engineer reaches a justified decision sooner, whether handoffs lose less context, or whether fewer investigations restart unnecessarily. Counting stored notes or generated summaries tells us little about those outcomes.&lt;/p>
&lt;h2 id="what-i-wanted-engineers-to-take-away">What I wanted engineers to take away&lt;/h2>
&lt;p>Speaking at KLA&amp;rsquo;s Engineering Conference was an opportunity to connect AI with a familiar engineering challenge: continuity across people, incidents, and time.&lt;/p>
&lt;p>The promise of a second brain is practical. It can help a team recover the rationale behind a choice, resume an investigation, and reuse an earned lesson with its limits intact. Reaching that point requires ongoing maintenance and deliberate human ownership.&lt;/p>
&lt;p>My closing question was: &lt;strong>Where does your team pay its highest memory tax?&lt;/strong>&lt;/p>
&lt;p>That is where I would start. Find the context that repeatedly disappears, preserve it with evidence and boundaries, and see whether the next decision improves.&lt;/p>
&lt;p>The
&lt;a href="https://github.com/jacobaloysious/second-brain-talk" target="_blank" rel="noopener">talk repository&lt;/a> contains the slide deck, speaker notes, and demo materials for exploring the approach in more detail.&lt;/p></description></item><item><title>Bringing My Blog Back with Codex Computer Use</title><link>https://jacobaloysious.in/post/bringing-my-blog-back-with-codex/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/bringing-my-blog-back-with-codex/</guid><description>&lt;p>My blog&amp;rsquo;s source code was sitting in GitHub. My domain, &lt;strong>jacobaloysious.in&lt;/strong>, was registered with GoDaddy. I had a Vercel account ready to use. Somewhere in the background, an old AWS setup was still involved.&lt;/p>
&lt;p>The goal sounded simple: publish the blog from GitHub using Vercel and make it accessible through my own domain.&lt;/p>
&lt;p>This time, I asked Codex to help do the work through Computer Use. Instead of following a deployment tutorial and switching between dashboards myself, I let it inspect the browser, configure the deployment, and trace the domain setup. I stayed involved when access or missing context required my input.&lt;/p>
&lt;p>It turned into a useful exercise in both AI-assisted work and remembering how my own website was wired together.&lt;/p>
&lt;h2 id="1-get-computer-use-working">1. Get Computer Use working&lt;/h2>
&lt;p>The first obstacle appeared before deployment even began. Although I was signed in to GitHub, Vercel, and GoDaddy in Chrome, Codex could not initially connect to my browser.&lt;/p>
&lt;p>The Computer Use setting showed:&lt;/p>
&lt;blockquote>
&lt;p>Disabled by your organization or unavailable in your region.&lt;/p>
&lt;/blockquote>
&lt;p>The message alone did not establish the cause. We checked the available setup guidance, but did not conclusively diagnose why the setting was disabled. I subsequently enabled Computer Use and asked Codex to continue.&lt;/p>
&lt;p>Once access was working, Codex could inspect my existing signed-in Chrome tabs and interact with the account dashboards.&lt;/p>
&lt;p>The first lesson: being signed in to a website and giving an assistant access to that browser are separate steps.&lt;/p>
&lt;h2 id="2-inspect-the-existing-hugo-project">2. Inspect the existing Hugo project&lt;/h2>
&lt;p>My Hugo source lived in a private GitHub repository.&lt;/p>
&lt;p>Codex inspected the project and its existing GitHub Actions workflow. That revealed several details that mattered for the new deployment:&lt;/p>
&lt;ul>
&lt;li>The site used Hugo with the Academic theme.&lt;/li>
&lt;li>The theme was included as a Git submodule.&lt;/li>
&lt;li>The existing build pinned a specific Hugo Extended version.&lt;/li>
&lt;li>A custom icon was copied into the theme before building.&lt;/li>
&lt;li>The previous workflow published the generated &lt;code>public&lt;/code> directory to AWS S3.&lt;/li>
&lt;/ul>
&lt;p>Reading the old workflow gave us a starting point for a compatible build. There was no need to combine a hosting migration with a theme or Hugo upgrade.&lt;/p>
&lt;h2 id="3-import-the-repository-into-vercel">3. Import the repository into Vercel&lt;/h2>
&lt;p>In the Vercel dashboard, Codex opened &lt;strong>Add New → Project&lt;/strong>, selected GitHub, and selected my blog repository.&lt;/p>
&lt;p>Vercel already had access to the repository, so it could import the private project without making the source code public.&lt;/p>
&lt;p>The project configuration was:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Setting&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Framework preset&lt;/td>
&lt;td>Hugo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Root directory&lt;/td>
&lt;td>Repository root&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Source branch&lt;/td>
&lt;td>The production branch&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Environment variable&lt;/td>
&lt;td>&lt;code>HUGO_VERSION&lt;/code>, matching the existing build&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Build output&lt;/td>
&lt;td>&lt;code>public&lt;/code> through the Hugo preset&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We overrode the build command to preserve the existing theme setup. The build initialized the Git submodules, copied my custom icon into the theme, and generated the static site with the intended production URL.&lt;/p>
&lt;p>A simplified example of the Hugo generation step is:&lt;/p>
&lt;pre>&lt;code class="language-sh">hugo --minify --baseURL https://example.com/
&lt;/code>&lt;/pre>
&lt;p>Use your own domain in place of &lt;code>example.com&lt;/code>, and include any preparation steps required by your theme.&lt;/p>
&lt;h2 id="4-deploy-and-check-the-actual-website">4. Deploy and check the actual website&lt;/h2>
&lt;p>The first Vercel deployment succeeded. The blog became available at its Vercel-provided address.&lt;/p>
&lt;p>Codex then opened the deployed site and checked more than the deployment success message. The homepage displayed my biography, profile image, styling, and recent posts. It also opened the &lt;strong>Leadership - Buy In | Meetings&lt;/strong> article and verified that its content loaded.&lt;/p>
&lt;p>At this point, the hosting part was working. The Vercel project was connected to GitHub so future pushes to the production branch could trigger new deployments.&lt;/p>
&lt;h2 id="5-add-the-custom-domain-in-vercel">5. Add the custom domain in Vercel&lt;/h2>
&lt;p>Next, Codex added &lt;code>jacobaloysious.in&lt;/code> to the project&amp;rsquo;s production environment.&lt;/p>
&lt;p>We kept the address without &lt;code>www&lt;/code> as the main domain. Codex also added &lt;code>www.jacobaloysious.in&lt;/code> and configured a &lt;strong>308 permanent redirect&lt;/strong> to &lt;code>jacobaloysious.in&lt;/code>.&lt;/p>
&lt;p>Vercel supplied the DNS records needed for this project:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type&lt;/th>
&lt;th>Name&lt;/th>
&lt;th>Destination&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A&lt;/td>
&lt;td>&lt;code>@&lt;/code>&lt;/td>
&lt;td>The IP address provided by Vercel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CNAME&lt;/td>
&lt;td>&lt;code>www&lt;/code>&lt;/td>
&lt;td>The CNAME target provided by Vercel&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The destinations are intentionally generalized here. Use the exact records displayed in your own Vercel dashboard.&lt;/p>
&lt;h2 id="6-discover-the-old-aws-connection">6. Discover the old AWS connection&lt;/h2>
&lt;p>I expected the next step to be editing DNS in GoDaddy. Instead, GoDaddy showed that the domain&amp;rsquo;s DNS was managed by &lt;strong>Amazon Route 53&lt;/strong>.&lt;/p>
&lt;p>That explained why registering the domain with GoDaddy did not mean GoDaddy controlled its active DNS records.&lt;/p>
&lt;p>The services had different jobs:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Service&lt;/th>
&lt;th>Role in this setup&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>GitHub&lt;/td>
&lt;td>Stored the Hugo source and content&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vercel&lt;/td>
&lt;td>Built and hosted the website&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GoDaddy&lt;/td>
&lt;td>Registered the domain; later became its DNS provider&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AWS&lt;/td>
&lt;td>Previously supplied Route 53 DNS and the S3 deployment destination&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Codex initially suggested updating the existing Route 53 records to preserve anything else using that DNS zone. I clarified that I no longer used the AWS setup and wanted the website routed to Vercel.&lt;/p>
&lt;p>That context mattered: it allowed us to replace the old nameserver delegation instead of requiring access to AWS.&lt;/p>
&lt;h2 id="7-move-dns-management-back-to-godaddy">7. Move DNS management back to GoDaddy&lt;/h2>
&lt;p>Codex selected GoDaddy&amp;rsquo;s default nameservers and completed the confirmation flow, moving DNS management back to GoDaddy.&lt;/p>
&lt;p>Once the GoDaddy DNS records became available, Codex made two changes:&lt;/p>
&lt;ol>
&lt;li>Replaced the parked &lt;code>@&lt;/code> A record with the IP address supplied by Vercel.&lt;/li>
&lt;li>Changed the &lt;code>www&lt;/code> CNAME to the target supplied by Vercel.&lt;/li>
&lt;/ol>
&lt;p>GoDaddy confirmed that both records were saved successfully.&lt;/p>
&lt;p>Changing DNS and retiring the previous hosting setup are separate tasks. A migration should also include a review of old deployment workflows, credentials, and billable resources.&lt;/p>
&lt;h2 id="8-investigate-vercels-invalid-configuration-warning">8. Investigate Vercel&amp;rsquo;s “Invalid Configuration” warning&lt;/h2>
&lt;p>After saving the records, Vercel still showed &lt;strong>Invalid Configuration&lt;/strong> for both domain names.&lt;/p>
&lt;p>It was tempting to assume we had entered something incorrectly. Codex compared the saved GoDaddy records with Vercel&amp;rsquo;s required values and ran public DNS checks.&lt;/p>
&lt;p>The records matched, but the public lookups were still reaching the old AWS nameservers. Those servers refused the queries, and the lookups returned &lt;code>SERVFAIL&lt;/code>.&lt;/p>
&lt;p>So there were two different states:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>In GoDaddy:&lt;/strong> the new nameservers and Vercel records were saved.&lt;/li>
&lt;li>&lt;strong>In the public DNS checks:&lt;/strong> the old AWS delegation was still being used.&lt;/li>
&lt;/ul>
&lt;p>Vercel could not verify the custom domain while those lookups were failing. A browser check also failed to resolve the domain, so we could not yet verify HTTPS on the custom address.&lt;/p>
&lt;p>GoDaddy advised that most DNS updates take effect within an hour, but global propagation can take up to 48 hours. Saving a DNS change is not the same as every resolver seeing it immediately.&lt;/p>
&lt;h2 id="9-verify-the-domain-and-go-live">9. Verify the domain and go live&lt;/h2>
&lt;p>After the DNS changes propagated, the domain was verified and the blog became accessible at &lt;strong>
&lt;a href="https://jacobaloysious.in" target="_blank" rel="noopener">jacobaloysious.in&lt;/a>&lt;/strong>.&lt;/p>
&lt;p>The completed setup:&lt;/p>
&lt;ul>
&lt;li>The Hugo blog was deployed and working on its Vercel address.&lt;/li>
&lt;li>GitHub was connected to Vercel for future deployments.&lt;/li>
&lt;li>The main domain and &lt;code>www&lt;/code> redirect were configured in Vercel.&lt;/li>
&lt;li>GoDaddy had accepted the nameserver change and saved the required records.&lt;/li>
&lt;li>The custom domain was verified and the website was live at &lt;code>jacobaloysious.in&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The earlier &lt;strong>Invalid Configuration&lt;/strong> warning was an intermediate state during the migration. Once the DNS change took effect, the main goal was complete: readers could use my own domain to access the blog hosted on Vercel.&lt;/p>
&lt;h2 id="what-i-learned-from-using-computer-use">What I learned from using Computer Use&lt;/h2>
&lt;p>The most useful part was watching Codex carry context across several services. It read the old build workflow, applied those details in Vercel, discovered the Route 53 dependency in GoDaddy, and used DNS checks to explain the remaining warning.&lt;/p>
&lt;p>It also exposed where my input was necessary. I had to enable browser access and explain that AWS was no longer part of the intended setup. The assistant could inspect the dashboards, but it could not infer that decision from a nameserver entry alone.&lt;/p>
&lt;p>There were practical limits too. Browser pages needed time to load, some interactions needed another attempt, and a successful save did not guarantee an immediately working domain. Verification remained part of the job.&lt;/p>
&lt;p>For me, this was a concrete example of AI helping with an operational task: taking an existing website, understanding enough of its history to deploy it, and working through the connections between source code, hosting, registration, and DNS.&lt;/p>
&lt;p>My blog is now live at &lt;strong>
&lt;a href="https://jacobaloysious.in" target="_blank" rel="noopener">jacobaloysious.in&lt;/a>&lt;/strong>, with the content in GitHub, hosting on Vercel, and domain registration and DNS managed through GoDaddy. The old AWS nameserver dependency has been replaced.&lt;/p>
&lt;p>For future posts, the publishing flow is simple: update the content in GitHub, push to the production branch, and let Vercel build and deploy the site.&lt;/p></description></item><item><title>PKCE</title><link>https://jacobaloysious.in/post/tech_pkce/</link><pubDate>Fri, 03 Dec 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_pkce/</guid><description>&lt;p>
&lt;a href="https://jacobaloysious.in/post/tech_oauth/" title="OAuth">Prerequisite: OAuth2.0&lt;/a>&lt;/p>
&lt;p>PKCE is pronounced as &amp;lsquo;pixie&amp;rsquo;. PKCE - Proof Key for Code Exchange =&amp;gt;
&lt;a href="https://www.youtube.com/watch?v=5cQNwifDq1U" target="_blank" rel="noopener">Credits&lt;/a>&lt;/p>
&lt;h3 id="concepts">Concepts:&lt;/h3>
&lt;h4 id="what-is-confidential-client">What is Confidential Client?&lt;/h4>
&lt;p>Any backend application written in languages like .Net, Java, NodeJS can be considered as Confidential Client. As they have the ability to &amp;ldquo;store variables&amp;rdquo; in our case Secret Keys - without exposing them to the end user - as the code is running on the server side. The secret key would be used to generate the Access Token in the OAuth Flow.&lt;/p>
&lt;h4 id="what-is-public-client">What is public Client?&lt;/h4>
&lt;ol>
&lt;li>
&lt;p>Any Single Page Application (SPA), where all the code is downloaded to the users browser would be considered a Public Client. Coz: the secret key cannot be securely stored as the hacker could do a &amp;ldquo;view source code&amp;rdquo; and look at the secret key which is usually in plain text/string format.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Another example are Mobile Apps - the user can download and de-compile/reverse engineer - the application and find the secret key.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="why-do-you-need-pkce">Why do you need PKCE?&lt;/h3>
&lt;p>Once the secret key is available - the hacker can impersonate as the Client and generate the access token (note: clientID is public and secret key is private). Once the access token is available - the hacker can access lot more confidential information about the user. So the existing
&lt;a href="https://jacobaloysious.in/post/tech_oauth/" title="OAuth">OAuth2.0&lt;/a> model is not secure for both SPA and mobile apps. To solve this challenge -
&lt;a href="https://www.rfc-editor.org/rfc/pdfrfc/rfc7636.txt.pdf" target="_blank" rel="noopener">PKCE&lt;/a> was introduced.&lt;/p>
&lt;h4 id="oauth-20-normal-flow">OAuth 2.0 Normal Flow:&lt;/h4>
&lt;p>&lt;img src="oauth-normal-flow.jpg" alt="alt Components" title="OAuth2.0 Normal Flow">&lt;/p>
&lt;h4 id="pkce-flow">PKCE Flow:&lt;/h4>
&lt;p>&lt;img src="pkce-flow-2.jpg" alt="alt Components" title="PKCE Flow">&lt;/p>
&lt;h3 id="what-is-new-in-pkce">What is new in PKCE?&lt;/h3>
&lt;ol>
&lt;li>In the PKCE flow: Instead of a constant Secret Key - Everytime an OAuth flow is initiated - &lt;strong>A new &amp;ldquo;Secret Key&amp;rdquo;&lt;/strong> is generated and Hashed.&lt;/li>
&lt;li>Hash is shared with the Authentication Server(AS) when requesting for the Temporary Code. AS would remember this Hash value.&lt;/li>
&lt;li>When the client request for the Access Token - it sends the newly generated &amp;ldquo;Secret Key&amp;rdquo; - the AS would check, if the secret matches the Hash (it already remembers - #2).&lt;/li>
&lt;/ol>
&lt;h3 id="reference">Reference:&lt;/h3>
&lt;p>
&lt;a href="https://www.rfc-editor.org/rfc/pdfrfc/rfc7636.txt.pdf" target="_blank" rel="noopener">RFC7636&lt;/a>,
&lt;a href="https://www.youtube.com/watch?v=aU9RsE4fcRM" target="_blank" rel="noopener">Oauth Hack&lt;/a>,
&lt;a href="https://www.youtube.com/watch?v=5cQNwifDq1U" target="_blank" rel="noopener">What&amp;rsquo;s the difference between Confidential and Public clients&lt;/a>&lt;/p></description></item><item><title>Designated Authentication - OAuth2.0</title><link>https://jacobaloysious.in/post/tech_oauth/</link><pubDate>Thu, 02 Dec 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_oauth/</guid><description>&lt;p>
&lt;a href="https://www.youtube.com/watch?v=996OiexHze0" target="_blank" rel="noopener">OAuth2.0 and OpenID&lt;/a> - is one of the best videos (62 mins) on this topic - Highly recommended. This blog is just a quick summary (4 mins read) of it.&lt;/p>
&lt;h3 id="what-is-oauth">What is OAuth?&lt;/h3>
&lt;p>OAuth is a security standard where you give one application permission to access your data from another application i.e. you authorized one application to use data from another application on your behalf, without giving them your username and password. The steps to grant permission(s) or consent is often reffered to as authorization or delegated authorization. Permissions are scoped e.g. client can be given access to read your GMail contacts but cannot Read/Write your email. OAuth is not exactly used for user identification (who loggged in), instead its used to access API&amp;rsquo;s for resources like gmail contact list.&lt;/p>
&lt;p>&lt;img src="access-card-example.jpg" alt="alt Components" title="Access Card Example">&lt;/p>
&lt;ul>
&lt;li>You check-in to a hotel.&lt;/li>
&lt;li>At the front desk - You give your details like Passport and Reservation&lt;/li>
&lt;li>Front Desk would in turn - give you the Key Card i.e. Access Token&lt;/li>
&lt;li>You can use the Key Card to access your reserved Room&lt;/li>
&lt;/ul>
&lt;p>Here, the Front Desk is the Authentication Server - the key card is your Access token used to acess your Room (Resources API). The API doesn&amp;rsquo;t really care who is accessing the Resource as long as they have the access token.&lt;/p>
&lt;h3 id="oauth-terminologies">OAuth Terminologies:&lt;/h3>
&lt;p>Lets assume you are giving Yelp access to your Gmail account contacts..&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Resource Owner:&lt;/strong> Represent you the owner of the gmail account; who can give permission/authorize.&lt;/li>
&lt;li>&lt;strong>Client&lt;/strong>: Represent Yelp i.e. the application which would like to perform action on behalf of you.&lt;/li>
&lt;li>&lt;strong>Authorization Server:&lt;/strong> One who validates the gmail uname/password and authenticate - in this case &lt;em>accounts.google.com&lt;/em>. Resource owner will already have an account with this server (gmail).&lt;/li>
&lt;li>&lt;strong>Resource Server:&lt;/strong> Server (API) which has the actual content/resources like contact list in this case &lt;em>contacts.google.com&lt;/em>. Which client would talk to get the actual data.&lt;/li>
&lt;li>&lt;strong>Authorization Grant:&lt;/strong> Once authenticated an &lt;em>Authorizatiion Code&lt;/em> or temporary code is sent back to he client. So, the client has been granted relevant access to the resource owners gmail contacts. This is short lived, this is used to inturn get the access token (long lived).&lt;/li>
&lt;li>&lt;strong>Redirect URI:&lt;/strong> Also known as Callback. It is the URI to which the response of Authentication #5 should be sent back to client #2&lt;/li>
&lt;li>&lt;strong>Access Token:&lt;/strong> Token/Key used by the Client to access all the resources of the owner; based on the authorized permissions.&lt;/li>
&lt;li>&lt;strong>Response Type&lt;/strong>: The type of information the client would like to receive e.g: code (authorization code)&lt;/li>
&lt;li>&lt;strong>Scope:&lt;/strong> Granular permission the client wants acess to like Read Contact, Read Profile basic info.&lt;/li>
&lt;li>&lt;strong>Consent:&lt;/strong> Whether the client can be given permission to access the requested information.&lt;/li>
&lt;li>&lt;strong>ClientID:&lt;/strong> Unique ID of the client, which is used to talk to the authorization server.&lt;/li>
&lt;li>&lt;strong>Client Secret:&lt;/strong> Secret Key - only the client and the auth server shares. Helps Client and Resource Server to share information secretly behind the scenes.&lt;/li>
&lt;/ol>
&lt;h3 id="oauth-flow">OAuth Flow:&lt;/h3>
&lt;h4 id="prerequisite">Prerequisite:&lt;/h4>
&lt;p>Client and the Authorization Server should establish a working relationship. Both the &amp;ldquo;ClientID (AppID)&amp;rdquo; and &amp;ldquo;Client Secret(AppSecret)&amp;rdquo; are generated by the Authorization Server. The client ID would be used to uniquely identify the client. Its the responsibility of the client to ensure the &amp;ldquo;ClientSecret&amp;rdquo; is kept secret and its only know by client and authorization server. As there are the only means for the Auth Server to verify the client.&lt;/p>
&lt;h4 id="actual-flow">Actual Flow:&lt;/h4>
&lt;p>In this example, Yelp would want to authenticate a user based on GMail Authentication, and access Profile/Contacts information.&lt;/p>
&lt;p>&lt;img src="oauth-code-flow.jpg" alt="alt Components" title="OAuth Code Flow">&lt;/p>
&lt;ol>
&lt;li>Client(Yelp) would make a request to Authenticate - to &amp;ldquo;Autorization Server&amp;rdquo; (accounts.google.com); as part of the request:
&lt;ol>
&lt;li>&amp;ldquo;RedirectURI&amp;rdquo; is set to a callback URL of the client &amp;ldquo;yelp.com/callback&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;Respose Type&amp;rdquo; is set to &amp;ldquo;Code&amp;rdquo;, so a auth code is sent back as response to 1.a&lt;/li>
&lt;li>&amp;ldquo;Client ID&amp;rdquo; and &amp;ldquo;Scope&amp;rdquo; (Profile/Contacts)&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Auth Server pops up a dialog (mostly) - where user gets to enter the GMail credentials (uname/pass) and authenticate&lt;/li>
&lt;li>Auth Server shows up a consent form - list the info access requested (like contacts, profile) - user gets to choose Yes or NO&lt;/li>
&lt;li>Once concent(Yes) is given - an &amp;ldquo;Auth Code&amp;rdquo; is generated and sent back to the the client - to specifiied callback/redirectURI.&lt;/li>
&lt;li>Then, the client sends the &amp;ldquo;Auth Code + ClientID + ClientSecret&amp;rdquo; to &amp;ldquo;Authorization Server&amp;rdquo; and gets back an &amp;ldquo;Access Token&amp;rdquo;&lt;/li>
&lt;li>Using the &amp;ldquo;Access Token&amp;rdquo;, Client can contact the resource server (contacts.google.com) to get the actual resources (profile, contacts) of the user.&lt;/li>
&lt;/ol>
&lt;p>Reference: &lt;a href="https://www.youtube.com/watch?v=aU9RsE4fcRM">https://www.youtube.com/watch?v=aU9RsE4fcRM&lt;/a>&lt;/p></description></item><item><title>WASM Cloud</title><link>https://jacobaloysious.in/post/tech_wasm_cloud/</link><pubDate>Fri, 26 Nov 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_wasm_cloud/</guid><description>&lt;h4 id="what-is-wasm-cloud-trying-to-solve">What is WASM Cloud trying to solve?&lt;/h4>
&lt;p>Lets ask - what a typical &amp;ldquo;Development Iteration&amp;rdquo; look like?&lt;/p>
&lt;ol>
&lt;li>Start of with your Napkin Drawings - conceptual vision of what is it you want to build&lt;/li>
&lt;li>Initial Prototype, prove that it works&lt;/li>
&lt;li>Then, Figure out how to scale&lt;/li>
&lt;/ol>
&lt;p>The challenge is between #2 and #3 there is more often a &lt;strong>complete rewrite&lt;/strong>. As the author claims- Virtually nothing from prototype is written in a way it is scalable.&lt;/p>
&lt;p>The goal of the project is to &lt;strong>&amp;ldquo;Ship Napkins to Production&amp;rdquo;&lt;/strong> directly.&lt;/p>
&lt;ol>
&lt;li>Draw your conceptual map of how your application should look like&lt;/li>
&lt;li>Fill in the blanks with your business logic and just push it - boom!!&lt;/li>
&lt;li>Don&amp;rsquo;t worry about any of the non-sense in between.&lt;/li>
&lt;li>Remove all the boiler plate and let developer write only Business logic.&lt;/li>
&lt;/ol>
&lt;h4 id="how-is-this-achived">How is this achived?&lt;/h4>
&lt;p>Using the &lt;strong>&amp;ldquo;Actor Model&amp;rdquo;&lt;/strong> and the out of the box boilerplate infrastructure.
Personally, I a fan of Actor Model, as its easy to just write a function which has a single responsibility. If you have ever worked with Threads and IPC(Interprocess communication) - you would know the overheads that come in managing them. With the Actor model you have an input queue, an output queue and the function in between. Developer shall write only the function. Function would read from the input queue, process and write it to the output queue. The infrastructure should take care of running the actor, as a thread or process and also manage the input/output queue, data transfer, serialization etc. One of the popular implementation of Actor model is
&lt;a href="https://akka.io/" target="_blank" rel="noopener">AKKA&lt;/a>.&lt;/p>
&lt;p>From the authors:&lt;/p>
&lt;blockquote>
&lt;p>The creators of wasmCloud believe that we can not only change the way developers build software for the better, but make it easier to secure, deploy, maintain, observe, and upgrade that software as well–all while reducing the amount of boilerplate we have to copy and paste.&lt;/p>
&lt;/blockquote>
&lt;h4 id="example">Example:&lt;/h4>
&lt;p>Lets look at their
&lt;a href="https://wasmcloud.dev/app-dev/create-actor/generate/" target="_blank" rel="noopener">Hello World&lt;/a> example.&lt;/p>
&lt;p>Interesting snippets:&lt;/p>
&lt;pre>&lt;code> #[derive(Debug, Default, Actor, HealthResponder)]
#[services(Actor, HttpServer)]
struct HelloActor {}
/// Implementation of HttpServer trait methods
#[async_trait]
impl HttpServer for HelloActor {
//Actual Impl
}
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>&lt;em>HelloActor&lt;/em> is the name of your actor&lt;/li>
&lt;li>The two lines with &amp;ldquo;#&amp;rdquo; above the actor name igenerate - at compile time - nearly all of the scaffolding needed to build an actor.&lt;/li>
&lt;li>The &lt;em>HealthResponder&lt;/em> term generates a function that automatically responds to health check queries from the &lt;em>wasmCloud&lt;/em> host.&lt;/li>
&lt;li>The &lt;em>#[services(&amp;hellip;)]&lt;/em> line declares the servicesthat your actor implements, and generates message handling code for those interfaces.&lt;/li>
&lt;li>All actors implement the &lt;em>Actor&lt;/em> interface.&lt;/li>
&lt;li>The &lt;em>HttpServer&lt;/em> entry declares that the actor will also implement that interface, and requires an implementation of that trait’s method: handle_request.&lt;/li>
&lt;/ul>
&lt;h4 id="conclusion">Conclusion:&lt;/h4>
&lt;p>I am fan of distributed computing: designing and writing scalable application. Maybe the concept of wasmCloud doing the heavy lifting sounds easy; but I belive its a very hard problem. Coz: when we actually get our hands dirty with actual application design - challenges like: managing states, versioning, schema management/registry, idempotency, trade offs w.r.t CAP theorm, heterogeneous system etc..all comes in. Also, given what Docker + Kubernetes eco system already provides - finding the right architecture to use wasmCloud would be a challenge. But this is definitely a place lot of things can be improved and room for optimization. Star&amp;rsquo;ed the project for now, will be following it closely.&lt;/p>
&lt;p>Reference:
&lt;a href="https://wasmcloud.dev/" target="_blank" rel="noopener">WASM Cloud&lt;/a>,
&lt;a href="https://github.com/wasmCloud/wasmCloud" target="_blank" rel="noopener">GitHub&lt;/a>&lt;/p></description></item><item><title>Rubber Duck Debugging</title><link>https://jacobaloysious.in/post/tech_rubber_duck_debugging/</link><pubDate>Wed, 24 Nov 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_rubber_duck_debugging/</guid><description>&lt;p>I have been in a similar situation multiple times - where in I was stuck on a problem. To get a solution, I would want to reach out for help. So, I would start to write it down - as it could post it in StackOverflow or send it as an email to my friend/colleague. While I am writing the problem down, I try to set the context and elaborate on the problem statement - after few iteration/reformatting/edits - its strange that I would have found the solution. Oops-What happened???&lt;/p>
&lt;p>Stepping back and looking at it, what had happened is - while we try to explain the problem to someone (in writing) - we try to remove all the clutter and focus on the actual problem - with the intent to keep the &lt;strong>email shorter and digestable components&lt;/strong>, so that the audience/reader is not overwhelmed. But, in the process we have a better clarity of the core (only) problem we are trying to solve. When that clarity comes up, more often we get closer to the solution.&lt;/p>
&lt;p>BTW: I never knew, there was a name for it - &lt;strong>&amp;ldquo;Rubber Duck Debugging&amp;rdquo;&lt;/strong> until I heard it from
&lt;a href="https://twitter.com/shanselman" target="_blank" rel="noopener">scott hanselman&lt;/a> in one of his podcast. And there is a website
&lt;a href="https://rubberduckdebugging.com/" target="_blank" rel="noopener">rubberduckdebugging&lt;/a> for it.&lt;/p>
&lt;p>Snip from the site:&lt;/p>
&lt;blockquote>
&lt;ol>
&lt;li>Place rubber duck on desk and inform it you are just going to go over some code with it, if that’s all right.&lt;/li>
&lt;li>Explain to the duck what your code is supposed to do, and then go into detail and explain your code line by line.&lt;/li>
&lt;li>At some point you will tell the duck what you are doing next and then realise that that is not in fact what you are actually doing. The duck will sit there serenely, happy in the knowledge that it has helped you on your way.&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;p>Well, I had to buy one rubber duck, as I wasn&amp;rsquo;t able to negotiate to lease one with my 7yr old daughter 😉&lt;/p>
&lt;p>The next time you get stuck on a hard issue, take a moment and write down exactly whats going wrong as if you were going to explain it to someone else, you may just notice exactly where you are going wrong and even if you dont, at least you have a question all ready for submission to StackOverflow&amp;hellip;&lt;/p></description></item><item><title>Software Enthusiast - Getting Started</title><link>https://jacobaloysious.in/post/tech_getting_started/</link><pubDate>Wed, 24 Nov 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_getting_started/</guid><description>&lt;p>I have been asked this question multiple times. I would like to be a software enthusiast and where do I get started. Thanks to my Friend to help put this together&lt;/p>
&lt;h3 id="books">Books:&lt;/h3>
&lt;h4 id="coding">Coding:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/clean-code-a/9780136083238/" target="_blank" rel="noopener">Clean Code: A Handbook of Agile Software Craftsmanship&lt;/a> *&lt;/li>
&lt;/ul>
&lt;h4 id="unit-testing">Unit Testing:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.manning.com/books/the-art-of-unit-testing-second-edition" target="_blank" rel="noopener">The Art of Unit Testing, 2nd Edition&lt;/a> *&lt;/li>
&lt;/ul>
&lt;h4 id="design">Design:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/design-patterns-elements/0201633612/" target="_blank" rel="noopener">Design Patterns: Elements of Reusable Object-Oriented Software&lt;/a>*&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/head-first-design/9781492077992/" target="_blank" rel="noopener">Head First Design Patterns, 2nd Edition&lt;/a> *&lt;/li>
&lt;/ul>
&lt;h4 id="solid-principles">SOLID principles:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://cleancoders.com/" target="_blank" rel="noopener">Clean Coders: Level up your code&lt;/a> *&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/" target="_blank" rel="noopener">Clean Architecture: A Craftsman’s Guide to Software Structure and Design&lt;/a> *&lt;/li>
&lt;/ul>
&lt;h4 id="refactoring">Refactoring:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/refactoring-improving-the/9780134757681/" target="_blank" rel="noopener">Refactoring: Improving the Design of Existing Code&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/working-effectively-with/0131177052/" target="_blank" rel="noopener">Working Effectively with Legacy Code&lt;/a>&lt;/li>
&lt;/ul>
&lt;h4 id="architecture">Architecture:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/" target="_blank" rel="noopener">Clean Architecture: A Craftsman’s Guide to Software Structure and Design&lt;/a> *&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/" target="_blank" rel="noopener">Designing Data-Intensive Applications&lt;/a> *&lt;/li>
&lt;li>
&lt;a href="https://martinfowler.com/architecture/" target="_blank" rel="noopener">Software Architecture Guide&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/building-microservices/9781491950340/" target="_blank" rel="noopener">Building Microservices&lt;/a>
&lt;em>*must read books&lt;/em>&lt;/li>
&lt;/ul>
&lt;h2 id="keeping-up-with-ever-evolving-tech-space">Keeping up with ever evolving tech Space:&lt;/h2>
&lt;h3 id="youtube-channels-to-follow">Youtube channels to follow&lt;/h3>
&lt;ul>
&lt;li>
&lt;a href="https://www.youtube.com/c/NDCConferences" target="_blank" rel="noopener">NDC Conferences&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.youtube.com/c/GotoConferences" target="_blank" rel="noopener">GOTO Conferences&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.youtube.com/nctv" target="_blank" rel="noopener">InfoQ&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="twitter-handles-to-follow">Twitter handles to follow:&lt;/h3>
&lt;ul>
&lt;li>
&lt;a href="https://twitter.com/allenholub" target="_blank" rel="noopener">allenholub&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/ploeh" target="_blank" rel="noopener">ploeh&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/jbogard" target="_blank" rel="noopener">jbogard&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/mfeathers" target="_blank" rel="noopener">mfeathers&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/GeePawHill" target="_blank" rel="noopener">GeePawHill&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/Grady_Booch" target="_blank" rel="noopener">Grady_Booch&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/shanselman" target="_blank" rel="noopener">shanselman&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/KentBeck" target="_blank" rel="noopener">KentBeck&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/martinfowler" target="_blank" rel="noopener">martinfowler&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/unclebobmartin" target="_blank" rel="noopener">unclebobmartin&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/ansonhorton" target="_blank" rel="noopener">Anson Horton&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/madstorgersen?lang=en" target="_blank" rel="noopener">Mads Torgersen&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/joboccara" target="_blank" rel="noopener">Jonathan Boccara&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/thecleancoders" target="_blank" rel="noopener">Clean Coders&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/davidfowl" target="_blank" rel="noopener">David Fowler&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/John_Papa" target="_blank" rel="noopener">John Papa&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/spolsky" target="_blank" rel="noopener">Joel Spolsky&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/robconery" target="_blank" rel="noopener">Rob Conery&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/codinghorror" target="_blank" rel="noopener">Jeff Atwood&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/migueldeicaza" target="_blank" rel="noopener">Miguel “mask and vax” de Icaza&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/haacked" target="_blank" rel="noopener">Boom Haackalacka&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://twitter.com/shanselman" target="_blank" rel="noopener">Scott Hanselman&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="podcasts--blogs-to-follow">Podcasts &amp;amp; blogs to follow:&lt;/h3>
&lt;ul>
&lt;li>
&lt;a href="https://www.dataengineeringpodcast.com/" target="_blank" rel="noopener">The Data Engineering Podcast&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.hanselminutes.com/" target="_blank" rel="noopener">Hanselminutes&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.thoughtworks.com/insights/podcasts/technology-podcasts/all-episodes" target="_blank" rel="noopener">Technology podcast: all episodes | Thoughtworks&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://netflixtechblog.com/" target="_blank" rel="noopener">Netflix TechBlog&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.thoughtworks.com/en-in/radar" target="_blank" rel="noopener">Technology Radar | An opinionated guide to technology frontiers | Thoughtworks&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://engineering.fb.com/" target="_blank" rel="noopener">Facebook Engineering Blog&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://engineeringblog.yelp.com/" target="_blank" rel="noopener">Yelp Engineering Blog&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://eng.uber.com/" target="_blank" rel="noopener">Uber Engineering Blog&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Reverse ETL</title><link>https://jacobaloysious.in/post/tech_reverse_etl/</link><pubDate>Sun, 14 Nov 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_reverse_etl/</guid><description>&lt;p>While listening to
&lt;a href="https://www.dataengineeringpodcast.com/reverse-etl-and-customer-data-platforms-episode-235/" target="_blank" rel="noopener">Podcast&lt;/a> stumbled upon this term of Reverse-ETL.&lt;/p>
&lt;p>First lets align on a few basic terminologies:&lt;/p>
&lt;h4 id="what-is-a-data-warehouse-dw">What is a Data Warehouse (DW):&lt;/h4>
&lt;p>Data Warehouse centralizes and consolidates data from multiple sources like application log files and transaction applications. Orgainzation use DW to gain valuable insights from data, which can improve decision making by building a historical record of data. It can be considered the single source of truth.&lt;/p>
&lt;h4 id="what-is-etl">What is ETL&lt;/h4>
&lt;p>&lt;img src="ETL.jpg" alt="alt Components" title="ETL">
ETL Stands for &lt;em>Extract Transform and Load&lt;/em>. ETL is a data integration process that combines data from multiple data sources into a single, consistent data store that is loaded into a data warehouse or other target system.&lt;/p>
&lt;h4 id="what-is-reverse-etl">What is Reverse ETL&lt;/h4>
&lt;p>&lt;img src="ReverseETL.jpg" alt="alt Components" title="Reverse ETL">
Reverse ETL is the process of copying data from a central data warehouse to operational systems of record, including but not limited to SaaS tools used for growth, marketing, sales and support.&lt;/p>
&lt;h3 id="discussion">Discussion:&lt;/h3>
&lt;p>Hang On! this doesn&amp;rsquo;t make sense - why should someone do ETL and then Reverse it? Coz, just ETL in itself - is a HUGE infrastructure to both develop and maintain - why would I manage both. Well, as always devil is in the details.&lt;/p>
&lt;p>Lets see an example from
&lt;a href="https://hightouch.io" target="_blank" rel="noopener">Hightouch&lt;/a>
&lt;img src="example.jpg" alt="alt Components" title="Example">&lt;/p>
&lt;p>&lt;strong>Reasoning&lt;/strong>(&lt;em>by Hightouch&lt;/em>):&lt;/p>
&lt;blockquote>
&lt;p>Reverse ETL is necessary because your data warehouse — the platform you bought to eliminate data silos — has ironically become a data silo. Without reverse ETL, your business’s core definitions only live in the warehouse.&lt;/p>
&lt;/blockquote>
&lt;p>More detailed information like use cases are avaialble in this blog
&lt;a href="https://hightouch.io/blog/reverse-etl/" target="_blank" rel="noopener">Reverse ETL&lt;/a>&lt;/p>
&lt;h3 id="conclusion">Conclusion:&lt;/h3>
&lt;p>The intent here seems to bridge the knowlege gap, that comes in due to data warehouse. Say: If a business user is so used to a particular platform/tech like SalesForce, the user would prefer the analystic output/data to be available in their platform. Incase of Data Warehouse, the SalesForce business user should understand and learn the new ways to access the data and its corresponding schema in the DW; and also learn the access mechanisms like scripts, sql etc.. With Reverse ETL, it creates a comfortable/known space for the user as all the &amp;ldquo;Actionable Data&amp;rdquo; from the analytics are available in their platform. So, sure it makes sense.&lt;/p>
&lt;p>Only challenge, I see as an organization is - the effort in maintaining both ETL and Reverse ETL infrastructure. Any changes in the DW schema - should be notified to both the teams and the deliverables should be synced up - to avoid any broken pipelines.&lt;/p>
&lt;p>Reference:
&lt;a href="https://www.oracle.com/in/database/what-is-a-data-warehouse/" target="_blank" rel="noopener">DataWarehouse&lt;/a>&lt;/p></description></item><item><title>Access Control - Casbin</title><link>https://jacobaloysious.in/post/tech_casbin/</link><pubDate>Sun, 31 Oct 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_casbin/</guid><description>&lt;p>I was trying to read about RBAB (Role-Based Access Control) and ABAC(Attribute-Based Access Control); after some reading - I realized, as a software engineer - it&amp;rsquo;s way more easier when you get to read code - So, I started to search for open source project which implements access control infrastructure, and stumbled upon
&lt;a href="https://casbin.org/" target="_blank" rel="noopener">Casbin&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Snip from Readme:&lt;/em> In Casbin, an access control model is abstracted into a CONF file based on the &lt;strong>PERM metamodel (Policy, Effect, Request, Matchers)&lt;/strong>. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules.&lt;/p>
&lt;/blockquote>
&lt;p>The interesting part here is how Casbin - has designed the whole system behind a simple yet flexible configuration file and grammars to describe predicates. Intent of this blog is - just to get you curious about Casbin. Casbin has reasonally good documentation on their site.&lt;/p>
&lt;p>&lt;em>ProTip:&lt;/em> Best way to understand an open source is to start with the test cases, look at each test case - review the context and assert statements.&lt;/p>
&lt;p>Lets look at a simple
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/examples/rbac_model.conf" target="_blank" rel="noopener">RBAC Model Config&lt;/a>&lt;/p>
&lt;pre>&lt;code>[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) &amp;amp;&amp;amp; r.obj == p.obj &amp;amp;&amp;amp; r.act == p.act
&lt;/code>&lt;/pre>
&lt;p>
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/examples/rbac_policy.csv" target="_blank" rel="noopener">RBAC_Policy.CSV&lt;/a>&lt;/p>
&lt;pre>&lt;code>p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
&lt;/code>&lt;/pre>
&lt;p>Basically there are four parts: Request, Policy, Matcher and Effect.
&lt;a href="https://casbin.org/docs/en/how-it-works" target="_blank" rel="noopener">Ref&lt;/a>&lt;/p>
&lt;p>&lt;strong>Request:&lt;/strong> Defines the parameter &lt;em>name&lt;/em> and &lt;em>order&lt;/em> which we should provide for access control matching function. This enables the code/model to be setup when the data is read from the source.&lt;/p>
&lt;p>&lt;strong>Policy:&lt;/strong> It&amp;rsquo;s the Access stratergy. This is how our internal system is designed. It defines the name and order of the fields in the &lt;em>Policy rule document&lt;/em>.&lt;/p>
&lt;p>&lt;strong>Matchers:&lt;/strong>: This is similar to a predicate function - which shall describe if the request allowed. Rules which help us to match the request and the policy.&lt;/p>
&lt;p>&lt;strong>Effect:&lt;/strong> It&amp;rsquo;s useful, when there are multiple patterns which would match and you would need to make a decision. There are defaults, here you get to override both allow and deny defaults. Refer:
&lt;a href="https://casbin.org/docs/en/syntax-for-models#policy-effect" target="_blank" rel="noopener">PolicyEffect&lt;/a>.&lt;/p>
&lt;h4 id="inheritance">Inheritance:&lt;/h4>
&lt;ul>
&lt;li>If A has role B, B has role C, then A has role C. This transitivity is infinite for now&lt;/li>
&lt;li>Casbin doesn&amp;rsquo;t distinguish role from user in its RBAC. They are all treated as strings.&lt;/li>
&lt;/ul>
&lt;pre>&lt;code>p, data2_admin, data2, read
g, alice, data2_admin
&lt;/code>&lt;/pre>
&lt;p>In the above example,&lt;/p>
&lt;ul>
&lt;li>data2_admin has read access to data2&lt;/li>
&lt;li>alice inherits/is a member of role data2_admin. alice here can be a user, resource or a role.&lt;/li>
&lt;/ul>
&lt;p>
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/tests/rbac/test_role_manager.py" target="_blank" rel="noopener">test_role_manager.py&lt;/a>&lt;/p>
&lt;pre>&lt;code> rm = get_role_manager()
#1
rm.add_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;)
rm.add_link(&amp;quot;u3&amp;quot;, &amp;quot;g2&amp;quot;)
self.assertTrue(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;))
self.assertFalse(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g2&amp;quot;))
self.assertCountEqual(rm.get_roles(&amp;quot;u1&amp;quot;), [&amp;quot;g1&amp;quot;])
self.assertCountEqual(rm.get_roles(&amp;quot;u2&amp;quot;), [&amp;quot;g1&amp;quot;])
#2
rm.add_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain1&amp;quot;)
self.assertTrue(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain1&amp;quot;))
self.assertFalse(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain2&amp;quot;))
&lt;/code>&lt;/pre>
&lt;h4 id="what-casbin-does-not-do">What Casbin does NOT do:&lt;/h4>
&lt;ul>
&lt;li>Authentication (aka verify username and password when a user logs in)&lt;/li>
&lt;li>manage the list of users or roles.&lt;/li>
&lt;/ul>
&lt;p>Reference:
&lt;a href="https://github.com/jacobaloysious/pycasbin" target="_blank" rel="noopener">Casbin&lt;/a>&lt;/p></description></item><item><title>Bitmap Indexed Storage</title><link>https://jacobaloysious.in/post/tech_bitmaped_indexed_storage/</link><pubDate>Sat, 10 Jul 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_bitmaped_indexed_storage/</guid><description>&lt;p>Always been facinated by the ways - data structure&amp;rsquo;s can enable better storage and faster queries.&lt;/p>
&lt;p>BitMapped Indexed Storage - is useful while using &lt;strong>Columnar Databases&lt;/strong> - where per column data is stored together.&lt;/p>
&lt;p>Often the number of &lt;strong>distinct&lt;/strong> values in a column is small compared to the total number of rows. In the below example: column &lt;em>sex&lt;/em> there are only two distinct value &lt;em>Female or Male&lt;/em> in the given table - the cardinality is really low.&lt;/p>
&lt;p>In such cases: We can now take a column with &lt;em>n&lt;/em> distinct values and turn it into &lt;em>n&lt;/em> separate bitmaps: one bitmap for each distinct value, with one bit for each row. The bit is 1 if the row has that value, and 0 if not.&lt;/p>
&lt;p>Picture is worth a thousand words.. here we you go..&lt;/p>
&lt;p>&lt;img src="dbimage.jpg" alt="alt Components" title="Work">&lt;/p>
&lt;p>Fantastic Query performance&amp;hellip; As the amount of data read from disk on to the memory is less - improved Disk Throughput.
As always, solution are for a specific problem. If you wanna do a range query, then this solution might not the best.&lt;/p>
&lt;p>If &lt;em>n&lt;/em> is very small, those bitmaps can be stored with one bit per row. But if &lt;em>n&lt;/em> is bigger, there will be a lot of zeros in most of the bitmaps (&lt;em>sparse&lt;/em>). In that case, the bitmaps can additionally be run-length encoded or might not be recommened.&lt;/p>
&lt;p>Reference:
&lt;a href="https://www.goodreads.com/book/show/23463279-designing-data-intensive-applications" target="_blank" rel="noopener">Designing Data-Intensive Applications, by Martin Kleppmann &lt;/a>&lt;/p></description></item><item><title>Sqlite - Blunders</title><link>https://jacobaloysious.in/post/tech_sqlitedb/</link><pubDate>Sat, 05 Jun 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_sqlitedb/</guid><description>&lt;h3 id="what-is-sqlite">What is Sqlite:&lt;/h3>
&lt;blockquote>
&lt;p>SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world.
SQLite is built into all mobile phones and most computers and comes bundled inside countless other applications that people use every day. More Info:
&lt;a href="https://www.sqlite.org/index.html" target="_blank" rel="noopener">SQLite&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="my-learning">My Learning:&lt;/h2>
&lt;p>Sqlite is a fantastic embedded database - light weight, easy to use and almost Zero Configuration. I would prefer to call it ZMQ of DataBase 😉&lt;/p>
&lt;h3 id="blunder">Blunder:&lt;/h3>
&lt;p>As part of our design: we had to persist a bunch of parametric information per user session (few MBs). This information is not required outside the user session. But, it would be reused when the user comes back. Session - is local running on a windows box. We decided to use sqlite as our backend persistence mechanism; as there was no installation or specific configuration required to set sqlite up.&lt;/p>
&lt;p>Since there are parallel read write happening, we had to use the
&lt;a href="https://sqlite.org/wal.html" target="_blank" rel="noopener">WAL: Write Ahead Logging&lt;/a> journal mode.&lt;/p>
&lt;p>We did couple of blunders:&lt;/p>
&lt;h4 id="blunder-1---insert-one-at-a-time">Blunder #1 - Insert ONE at a time&lt;/h4>
&lt;ol>
&lt;li>When an action occured - we opened the db connection, inserted record and closed it.&lt;/li>
&lt;li>We didn&amp;rsquo;t want to keep the connection open, throughout the session - as there were chances of software crash - which could potentially corrupt he database.&lt;/li>
&lt;li>But as time went by, the number of actions became HUGE (order of millions - automated). At this point - Opening and closing the connection for every action, started to hurt us (&lt;em>high latency and low throughtput&lt;/em>). Coz, the time taken to open and close a connection (~300ms) was adding up.&lt;/li>
&lt;/ol>
&lt;p>Solution:
&lt;strong>Microbatching&lt;/strong>: Batched about 500 actions together - open the connection once, inserted all 500 actions in one shot and closed - Transaction. Worst case, we would loose less than 500 actions, and that tradeoff was acceptable.&lt;/p>
&lt;h4 id="blunder-2---mounted-drive">Blunder #2 - Mounted Drive:&lt;/h4>
&lt;ol>
&lt;li>The db had to be persisted on a mounted Linux drive (&lt;em>there are reason for this, I won&amp;rsquo;t go into details&lt;/em>).&lt;/li>
&lt;li>As the session started - the app created a sqliteDB directly on the mounted drive and started inserting.&lt;/li>
&lt;li>Since we were using WAL mode - there were temporary files getting created and deleted on the mounted drive during every transaction.&lt;/li>
&lt;li>As time went by - the reliablity of the drive/driver went down, and retries didn&amp;rsquo;t help keep up. The throughput significantly got impacted and multiple errors started showing up.&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="mounted_drive.jpg" alt="alt Components" title="Mounted Drive">&lt;/p>
&lt;p>&lt;strong>Solution:&lt;/strong> During the session, create the db in a &lt;strong>local (temp) drive&lt;/strong>, and end of sesssion copy it into the mounted drive. Similarly, when session restarts - copy from mounted drive on to a new temp location and consume it; and copy it back on completion.&lt;/p>
&lt;p>&lt;img src="temp_drive.jpg" alt="alt Components" title="Temp Drive">&lt;/p>
&lt;blockquote>
&lt;p>Never EVER - read or write an Sqlite Database from a mounted Drive. It usually works, but when it doesn&amp;rsquo;t - its a nightmare.&lt;/p>
&lt;/blockquote>
&lt;h4 id="blunder-3---lazy-load">Blunder #3 - Lazy Load:&lt;/h4>
&lt;ol>
&lt;li>When a specific information was required - we opened the db connection, executed the &amp;ldquo;select&amp;rdquo; query and return the results.&lt;/li>
&lt;li>Again: We didn&amp;rsquo;t want to keep the connection open, throughout the session - as there were chances of software crash - which could potentially corrupt he database.&lt;/li>
&lt;li>The read was implemented in a &amp;ldquo;Lazy - On demand&amp;rdquo; fashion - so that &lt;strong>only&lt;/strong> the minimum required information was read on demand.&lt;/li>
&lt;li>Reading only the required info is an ideal design - why would we ever read more that what is required? GraphQL - is a good example of how to design minimilistic API.&lt;/li>
&lt;li>But the challenge in our (specific) case: was the number of reads - it was about 600reads in less than 50 milliseconds. And each one had to open and close the connection (which takes ~50ms).&lt;/li>
&lt;li>Since it was a high throughput system, the overhead of lazy read was slowing down the entire software.&lt;/li>
&lt;/ol>
&lt;p>Solution: &lt;strong>PreFetch Require Information&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Since we knew the sequence in which the actions are performed, we redesigned the query to read &lt;strong>&amp;ldquo;more&amp;rdquo;&lt;/strong> records than required.&lt;/li>
&lt;li>Then created an in memory cache [index/map] to hold the data and filter based on Client Query.&lt;/li>
&lt;li>Read from DB would occur only if there is a cache miss.&lt;/li>
&lt;li>The cache was a rolling buffer with LRU (Least Recently Used) and a watermark (memory or count), so that the RAM didn&amp;rsquo;t explode.&lt;/li>
&lt;li>When a new read from DB was required, if the cache is full - we delete the oldest item and read from DB and add it to cache.&lt;/li>
&lt;li>No change to the Client API. Only the Data Acess Layer changed. 😉&lt;/li>
&lt;/ul>
&lt;h3 id="conclusion">Conclusion:&lt;/h3>
&lt;p>As always on hindsight, the solution looks trivial and obvious. Since, we were using Sqlite for the first time and initially didn&amp;rsquo;t reallize the data could grow exponentially - had to learn (Adapt) the hard way. Esp, when these are running in production - you need scheme&amp;rsquo;s to manage old vs new verisons of software (read, backward compatibility).&lt;/p></description></item><item><title>Concurrent Dictionary and Delay - C#</title><link>https://jacobaloysious.in/post/tech_concruent_dictionary/</link><pubDate>Sun, 30 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_concruent_dictionary/</guid><description>&lt;p>Reference:
&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-5.0" target="_blank" rel="noopener">ConcurrentDictionary&lt;/a>&lt;/p>
&lt;p>In a high throuput and low latency enviorment - we had a peice of code in the critical path - which helped in throttling. After version #2.0 of the software was released (internally) - we saw the througput has gone down significantly almost 3X - compared to previous Version #1.0 - 4mins to 11 minutes.&lt;/p>
&lt;p>We looked at the history/change sets between the versions: it was huge, 100s of commits and software was running with 100s of threads - on a 2Milliion LOC software stack. So, we were unsure which peice of code nor thread - was causing the delay. We were all over the place, trying to see if its a system issue or memory trend or some fragmentation vs memory/back pressure vs excess logging etc&amp;hellip;. And, after 3 day (yes: 3days slog) - we found its a delay caused by the &lt;strong>ConcurrentDictionary&lt;/strong> class!!! 😳&lt;/p>
&lt;p>It was an unpleasant suprise, since we didn&amp;rsquo;t expect such a huge delay cause due to a &lt;em>.Net Framework class&lt;/em>.&lt;/p>
&lt;h4 id="1-version-10--4mins">1. Version 1.0: [4mins]&lt;/h4>
&lt;p>One API with its backing data store with one Dictionary.&lt;/p>
&lt;pre>&lt;code> private Dictionary&amp;lt;int, User&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetUsers() {
return userInfos.Values;
}
&lt;/code>&lt;/pre>
&lt;h4 id="2-version-20--11-minutes">2. Version 2.0: [11 minutes]&lt;/h4>
&lt;p>In version #2: We changed the data store, which was holding the user info from a simple dictionary to a two level &lt;em>ConcurrentDictionay&lt;/em>.&lt;/p>
&lt;p>There was some obvious reason for using &lt;em>ConcurrentDictionay&lt;/em> - I won&amp;rsquo;t go into the details. But, after changing the data store- we ended up blindly updating all usages to consume from the new data store.&lt;/p>
&lt;p>And the API (GetUsers) implementation was updated, interface remained the same - but: it was in the critical path.&lt;/p>
&lt;pre>&lt;code> private ConcurrentDictionary&amp;lt;int, ConcurrentDictionary&amp;lt;int, User&amp;gt;&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetUsers(){
var users = new List&amp;lt;User&amp;gt;();
for(var userGroup in userInfos.Values){
for(var user in userGroup.Values){
users.add(user)
}
return users;
}
}
&lt;/code>&lt;/pre>
&lt;h4 id="3-version-30--4-minutes">3. Version 3.0: [4 minutes]&lt;/h4>
&lt;p>After looking into the usages &lt;strong>&amp;ldquo;in the critical path&amp;rdquo;&lt;/strong> - we only required the count of the active user groups.&lt;/p>
&lt;p>So, we wrote another API to just return the count. With this change, we were able to bring it back the throughput back to #Version 1.0. Huff!! 😌&lt;/p>
&lt;pre>&lt;code> private ConcurrentDictionary&amp;lt;int, ConcurrentDictionary&amp;lt;int, User&amp;gt;&amp;gt; userInfos;
public List&amp;lt;User&amp;gt; GetActiveUserGroups(){
return userInfos.Count;
}
&lt;/code>&lt;/pre>
&lt;h3 id="learning">Learning:&lt;/h3>
&lt;p>On highsight, the fix looks trivial and straight forward. But the challenge was the huge debugging effort to find the root cause. And little did we suspect a .Net Framework class can cause such a huge delay and slow down overall execution time. It was a great learning for us as a team - involved in debugging. BTW, we are talking about a code base of about few Million lines of code.&lt;/p>
&lt;p>BTW: Concurrent dictionary exists for a reason: Usage of it as our data store was the right choice.&lt;/p>
&lt;blockquote>
&lt;p>Represents a thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently.&lt;/p>
&lt;/blockquote>
&lt;p>If a class should be thread safe, then there has to be a lock/Mutex implemented. A lock has its own tradeoff in terms of execution time (Thread Scheduling, Prempt etc..) - so think carefully on its usages.&lt;/p></description></item><item><title>Parallel Processing with Constraints</title><link>https://jacobaloysious.in/post/tech_context_parallel_process/</link><pubDate>Sat, 15 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_context_parallel_process/</guid><description>&lt;p>An interesting senario, I recently encountered - have tried to tear down the context to get to the solution early, and avoid nitty gritty (boring) details. Also, example showns only 2 threads.&lt;/p>
&lt;h3 id="context">Context:&lt;/h3>
&lt;p>Different Actions (like: 1,2,3) from Cutomers (like Mr.A, Miss.B..) are pushed into a stream/queue from an external component. &lt;strong>&amp;ldquo;$&amp;rdquo;&lt;/strong> denotes end/last action from a customer.
A thread can 1) Process each action 2) Generate aggregated views for each customer per session - and then write it to the sink. The processing threads are started when the session starts and a user cannot reenter the session once done.&lt;/p>
&lt;p>note: all threads mentioned are background and long running.&lt;/p>
&lt;h3 id="constraints">Constraints:&lt;/h3>
&lt;p>Aggregation can be triggered only after all the actions are processed. i.e processing of action A1, A2, A3 should be done - before aggregation &amp;ldquo;A$&amp;rdquo; of A is triggered.&lt;/p>
&lt;h2 id="1-single-thread">1. Single Thread&lt;/h2>
&lt;p>Well, this is a no brainer. We have one thread which reads (pull) from the stream/queue and process one item from the queue at a time. All constraints can be managed with a global context inside the thread.&lt;/p>
&lt;p>&lt;img src="SingleThread.jpg" alt="alt Components" title="SingleThread">&lt;/p>
&lt;p>Below is a code which runs inside the Thread.&lt;/p>
&lt;pre>&lt;code> # {name, [actions]}
customer = {}
def read_from_stream(name, action):
if action=='$':
aggregate(name)
else:
process(name, action)
def process(name, action)
result = process_the_custom_action(action)
cusomter[name].append(result)
def aggregate(name):
action_results = cutomer[name]
view = gen_agg_view(action_results)
write_to_sink(view)
# remove customer from tracking
del customer[name]
def process_the_custom_action(action):
#Heavy lifting operation
return result
&lt;/code>&lt;/pre>
&lt;h2 id="2-pull-model---parallel-processing">2. Pull Model - Parallel Processing&lt;/h2>
&lt;p>Later we realized, the function &lt;strong>processing_the_custom_action&lt;/strong> is heavy lifting and takes more CPU. So, we were not able to keep up, with the incoming data rate. So, the memory increased drastically and triggers garbage collection and slow down actual code execution.&lt;/p>
&lt;p>So, we decided to introduce multiple threads to read from the queue. Same code as above - executed in multiple threads. Each thread would pop (PULL) one item from the queue, process it and then pop the next item. Just to be explicit: one item from the queue is processed by only one of the threads, but which thread (order) gets which item is not guranteed.&lt;/p>
&lt;p>&lt;img src="MultiThreadPullModel.jpg" alt="alt Components" title="PullModel">&lt;/p>
&lt;p>This design had a major challenge in ensuring the constraints are honored.&lt;/p>
&lt;p>In the above example: Aggregation for customer &amp;ldquo;A$&amp;rdquo; should be triggered only after last action &amp;ldquo;A3&amp;rdquo; is done. But &amp;ldquo;A$&amp;rdquo; and &amp;ldquo;A3&amp;rdquo; are processed in two different threads i.e. A3 by Thread #1; A$ by Thread #2. How would Thread #2 know A3 processing is done?? 😞&lt;/p>
&lt;p>If we really need to go with this approach and also honor the constraints - then there should be &lt;strong>broadcast events fired&lt;/strong> and all threads subscribed to it. Each thread should subscribe and do individual book keeping for all customers being processed across all the threads (dependency across threads, coupling..huff!!) 😰&lt;/p>
&lt;p>&lt;strong>Pros:&lt;/strong>
If any of the thread is killed in between. The data loss would be minimal i.e only the customers already read by that specific thread in the past and not completed. As all NEW data would be processed by one of the threads alive.&lt;/p>
&lt;h2 id="3-push-model---parallel-processing">3. Push Model - Parallel Processing&lt;/h2>
&lt;p>To ensure, we honor the constraint and also enable parallel processing. We introduced a load_balancer, called it a &lt;strong>distributor module&lt;/strong>.&lt;/p>
&lt;p>&lt;img src="MultiThreadPushModel.jpg" alt="alt Components" title="PushModel">&lt;/p>
&lt;h4 id="distributor-module">Distributor Module:&lt;/h4>
&lt;ul>
&lt;li>Would have reference to all the available processing threads.&lt;/li>
&lt;li>Each thread would have a corresponding queue. So, No_Of_Threads equals No_Of_Queueus.&lt;/li>
&lt;li>Each queue would have an index, which range from 0 to No_of_threads.
&lt;blockquote>
&lt;p>0 &amp;lt; Queue_Index &amp;lt; No_Of_Threads&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>Once an item is read - it generates a hash using the &amp;ldquo;name&amp;rdquo; of the customer. (like: SHA256)&lt;/li>
&lt;li>On the HASH we apply a MOD using the No_Of_Threads available and get the queue index
&lt;blockquote>
&lt;p>Queue_Index = hash(customer_name) % No_Of_Threads&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>Push the item to the corresponding queue_index&lt;/li>
&lt;/ul>
&lt;p>With this approach, we ensured - &lt;strong>same customer items are always processed by the same thread&lt;/strong>. So, there are no cross talks across threads or external dependency.
We could increase or decrease the number of threads used (obviously: before a session starts not in-between), based on a configuration or heuristics.&lt;/p>
&lt;pre>&lt;code> #### Distributor Module
def read_from_stream(name, action):
# Generate hashkey using customer name.
# Mod using the number of threads. No_Of_Thread == No_Of_Queues
queue_index = hash(name) % No_Of_Threads
publish(queue_index, name, action)
def publish(queue_index, name, action):
# get the queue reference using the index
queue = queues[queue_index]
# add the item into corresponding queue
queue.push(name, action)
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Cons:&lt;/strong>
The biggest cons: If a thread is killed for what ever reason. All the customers past and future once - which would be queue/allocated to the thread would be dropped. But in the pull model, it would have dropped only a handful sitting inside (past) the thread. Well we could handle this dynamically - like have a &lt;strong>heart beat&lt;/strong> mechanism and update the &amp;ldquo;No_Of_Thread&amp;rdquo; and get it working - just that it would be another infra.&lt;/p>
&lt;h4 id="conclusion">Conclusion:&lt;/h4>
&lt;p>There are pros and cons to both approaches.&lt;/p>
&lt;blockquote>
&lt;p>More Infra == More Code == More Maintainability == More to Debug == More room for bugs to creep in.&lt;/p>
&lt;/blockquote>
&lt;p>Trying to keep it simple while keeping the tradeoffs minimal is the key. In our case; we went with the push approach - as the chances of a thread crash was minimal and also we added some defensive coding and drop an item safely if required.&lt;/p>
&lt;h4 id="reference">Reference:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.manning.com/books/big-data" target="_blank" rel="noopener">Storm Model, by Nathan Marz&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Choreography vs Orchestration</title><link>https://jacobaloysious.in/post/tech_choreography/</link><pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_choreography/</guid><description>&lt;p>I have been getting into this debate quite often; which approach is preferred - Choreography vs Orchestrator. As always, it depends - Here is my take on it&amp;hellip;&lt;/p>
&lt;h3 id="modules">Modules:&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>App:&lt;/strong> Like an UI module, where user trigger&amp;rsquo;s an action like:Create an account&lt;/li>
&lt;li>&lt;strong>Action #N:&lt;/strong> Specific modules, which has one specific action to be performed like: sending email, mail atm cards - once an account is created.&lt;/li>
&lt;li>&lt;strong>Coordinator:&lt;/strong> Module, which handles/triggers all the downstream actions (#n) - when an event is recived from App.&lt;/li>
&lt;/ul>
&lt;h1 id="orchestrator">Orchestrator&lt;/h1>
&lt;p>&lt;img src="orchestration.jpg" alt="alt Components" title="Orchestrator">&lt;/p>
&lt;p>Here the coordinator is the orchestrator - which has the knowledge of the business rules - like: what are the set of actions that need to be performed when a specific event occured. If you observe, the arrows are pointing from coordinator towards actions. So, the coordinator need to understand the details of each and every API (per Actions) - their base URL/REST end points. And if each of them takes in differnt params - then all this information would sit inside the coordinator module.&lt;/p>
&lt;p>The cooridnator has become the central brain. And now it has tight coupling with all the Actions. And if a new action should be added, we will have changes to the coordinator module. After a point of time - this module will become the &amp;ldquo;God like&amp;rdquo; service. And every new Actions team would have to come to coordinator team (more meetings/discussion/followps) - to have their action integrated with the coordinator. Any changes to the coordinator module would become extremely hard - as there is so much coupling. And in addition failures of each action should be handled with special case - adding more and more actions specific details/code into the coordinator module.&lt;/p>
&lt;p>On deployment and version management: If there are specific changes to an Actions - URI or params. Either the action module - can never depricate their existing uri and params, as it is used by coordinator module. Or, coordinator module would have to understand the versions (of action) and act accordingly. Both can get a bit too messy. And choosing one or either of them - would require some tradeoffs (debates) to be made somewhere.&lt;/p>
&lt;p>BTW: When we are just starting of - coordinator might be the easiest way to get things done (read, move faster). But as we scale out - we should look for approaches which would enable better decoupling.&lt;/p>
&lt;p>&lt;strong>Pros:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Better control in coordinating the Action modules&lt;/li>
&lt;li>Handling failures would be easiers - in one place&lt;/li>
&lt;li>One place to look at the flow chart&lt;/li>
&lt;li>Good for transaction driven and places where actions are not idempotenent&lt;/li>
&lt;/ul>
&lt;h1 id="choreography">Choreography:&lt;/h1>
&lt;p>&lt;img src="choreography.jpg" alt="alt Components" title="Choreography">&lt;/p>
&lt;p>In this approach, lets take a broker like KAFKA as an example. When a new event is created its publised to a KAFKA queue with a specific TOPIC. There could be multiple subscribers (actions) - which would subscribe to the topic. The subscribers have their specific logics and subscibe to specific topics. At any point of time, any new actions can be added into the system without the knowledge of the App module. Look at the red arrows - there are NO arrow pointing from App towards specific Actions - so app doesn&amp;rsquo;t have any dependency on Action. We could also have custom subscribers (Actions) - which would run a pipeline for the data science team - this would enable us to generate observability data on the loads and trends.&lt;/p>
&lt;p>Deploy: In this approach both app and action moudles can be indeopendently deployed and upgraded. The schema registry would have the contract details - topic vs the payloads.&lt;/p>
&lt;p>&lt;strong>Schema Registry:&lt;/strong>
Every payload schema: should be published in the schema repository. We can use libraries like &amp;ldquo;Protocol Buffers&amp;rdquo; or &amp;ldquo;Apache Avro&amp;rdquo; for describing payload schemas.&lt;/p>
&lt;p>&lt;strong>Cons:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Handling errors might become very tricky. You would need an external monitoring infrastructure to track this.&lt;/li>
&lt;li>If there are depenencies across actions - then you should think twice on using this pattern. Though there are ways to solve it - like: using sub topics.&lt;/li>
&lt;li>Debugging can become a bit more hard. Needs distributed tracing to correlate/debug.&lt;/li>
&lt;/ul>
&lt;h1 id="conclusion">Conclusion:&lt;/h1>
&lt;p>Personal preference is to lean towards choreographed approach - as they are more loosely coupled and are more flexibble and amenable to change. Again: it all depends on your use case - if you are building a low latency system - which should catch errors and react to it really fast based on lots of buisinees logic, then orchestrator might be a better approach.&lt;/p>
&lt;h2 id="references">References:&lt;/h2>
&lt;p>
&lt;a href="https://samnewman.io/books/building_microservices/" target="_blank" rel="noopener">Book : Building Microservices by Sam Newman&lt;/a>&lt;/p></description></item><item><title>Feature Toggle - Nightmares</title><link>https://jacobaloysious.in/post/tech_feature_toggle/</link><pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_feature_toggle/</guid><description>&lt;p>We were working on a custom algorithm to optimally grab images given a list of area. Every image grabbed is written into an object store with a generated hashkey. The haskkey can be used to query the recorded data - when we would like to play them back.&lt;/p>
&lt;p>Software was a monolith and the algorithm was shipped with it. So, any new fixes would be shipped with the new software versions and user would be able to playback the same recorded data.&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locations = []
# actual algo...
return locations;
&lt;/code>&lt;/pre>
&lt;p>A day came when we - rolled out a bug fix in the algorithm. But, we broke the backward compatiblity 😟. Users had lots of existing recorded data which they wanted to playback. Since the algorithm had changed - the generated &lt;strong>hashkeys&lt;/strong> didn&amp;rsquo;t match the ones in the object store.&lt;/p>
&lt;p>We thought its proabaly a one off scenario: so we added a feature toggle - user will have to &lt;strong>manually&lt;/strong> (pain) figure out - which version the data was recorded on and update the toggle config (xml) and rerun.&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locs = []
if version == '1.0':
locs = oldAlgo()
else:
locs = newAlgo()
return locs
&lt;/code>&lt;/pre>
&lt;p>And - Well there was another bug fix:&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locs = []
if version == '1.0':
locs = Algo_1_0()
else if version == ''2.0':
locs = Algo_2_0()
else:
locs = latestAlgo()
return locs
&lt;/code>&lt;/pre>
&lt;p>And yeah the story went on for one more iteration and we had to STOP!!!! 😓 It was already becoming a nightmare to mantain the code.&lt;/p>
&lt;p>It wasn&amp;rsquo;t just manually figuring out the toggle which was creating pain - but to enable code reuse, the algorthm had to be refactored multiple times - so that parts of the algorithm can be reused across versions. In addition to that the number of test suites we had to manage had gone up significantly, as there were multiple users each one using different version - and oh yeah there were version specific fixes released as patches 😟&lt;/p>
&lt;p>Finally we decided to write all the &lt;strong>metadata&lt;/strong> - related to recording - into an embedded database. And on playback we get the information from the database and not by re-running the code/algorithm.&lt;/p>
&lt;p>On hindsight: the database feels like it an obvious solution, right? well the devil is in the details - in our case it about effort(read, time). The playback infrastructure based on database took ~8 weeks to build -first version. It had to handle numerous (10s) use cases. Previously, we only had one flow to be maintained i.e the flow which ran the use cases + algo shipped. But now we have to manage &lt;strong>TWO&lt;/strong> different flows - one for recording and one for playback (x) No_Of_Use_Cases.&lt;/p>
&lt;p>Its almost 3yrs since we rolled out this solution to production. When we look back it was a very good decision to build two different workflows 😍. Code is more structured and maintainable.&lt;/p>
&lt;p>At times - short term goal/fix takes priority. But, its always good to step back and look at the pain points and have an item in your backlog to find a better solution. The priority should be driven based on feedback: remember the 80/20 rule&lt;/p>
&lt;blockquote>
&lt;p>80 percent of customers only use 20 percent of the features in the software they’ve bought.&lt;/p>
&lt;/blockquote>
&lt;p>BTW: the database schema has changed a lot - with the addition of multiple use cases. Which also needs version management 😉 And evey new use case discussion now has two parts to be discussed - some problems are good to have (read, tradeoffs).&lt;/p>
&lt;p>Ref:
&lt;a href="https://martinfowler.com/articles/feature-toggles.html" target="_blank" rel="noopener">Feature Toggle&lt;/a>&lt;/p></description></item><item><title>Side Cars</title><link>https://jacobaloysious.in/post/tech_sidecars/</link><pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_sidecars/</guid><description>&lt;h2 id="idea">Idea&lt;/h2>
&lt;p>Add additional functionality into an existing application, with minimal or zero code changes to the exiting application. Also, the package can be consume by any software - irrespective of the language or framework (heterogeneous) it is written in.&lt;/p>
&lt;h2 id="how">How?&lt;/h2>
&lt;p>Using side cars pattern - : I/O(
&lt;a href="https://zeromq.org" target="_blank" rel="noopener">ZMQ&lt;/a>) + Topic (intent) + Data(
&lt;a href="https://developers.google.com/protocol-buffers" target="_blank" rel="noopener">ProtoBuf&lt;/a>) + Impl(Subscriber of topic).&lt;/p>
&lt;p>&lt;img src="sidecar.jpg" alt="alt Components" title="SideCarBase Window Images">&lt;/p>
&lt;p>DAPR framework from Microsoft Azure team – uses a similar design - runs side car. Dapr is a portable, event-driven runtime that makes it easy for developers to build resilient, micro service stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks.&lt;/p>
&lt;p>We could use the Side Car for adding functionalities like resource monitoring, logging, log aggregation, configurations, observability (telemetry /feature trends) etc.. Once we get the sidecar installed - we could keep adding new functionality as it independently developed and deployed. The features in the sidecar could be turned on or off based on options.&lt;/p>
&lt;p>Say as an organization: we decided to use cloud solutions for telemetry and internal tools decide to push data to the cloud - this solution might need a security layer - which would implement both gov and corp policies. This common implemenation of policies could be sitting inside the side cars and can be deployed on to all machine where the internal tools runs. And any update made to the policies would be transparent to the application - as the side cars would be independently deployed and maintained.&lt;/p>
&lt;h2 id="how-is-it-different-from-a-microservice-with-rest-api">How is it different from a Microservice with REST API?&lt;/h2>
&lt;p>Microservice doesn’t really care about the client. But here the side car is tightly coupled and tracks everything about the main application – including say: killing the application if the resource used is above the threshold - so that the tool PC doesn’t require a restart.&lt;/p>
&lt;h2 id="where-should-it-be-used">Where should it be used?&lt;/h2>
&lt;p>Only for non-critical operation - as the latency will be high.&lt;/p>
&lt;h3 id="reference">Reference:&lt;/h3>
&lt;ul>
&lt;li>
&lt;a href="https://dapr.io/" target="_blank" rel="noopener">DAPR&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.magalix.com/blog/the-sidecar-pattern" target="_blank" rel="noopener">Blog&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar" target="_blank" rel="noopener">MSSideCar&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>MongoDB - Hybrid Schema</title><link>https://jacobaloysious.in/post/tech_mogodb-hybridschemadesign/</link><pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_mogodb-hybridschemadesign/</guid><description>&lt;p>I recently ran into a situation where in we have a session and each Session produces a lot of messages (sync) and each message has an associated timestamp and order.
This is similar to a blog post (session) and its comments (messages).&lt;/p>
&lt;p>Since the messages are unbounded (not sure how many would be produced). In my initial design - I had a separate collection for Messages. Each Session would have one corresponding MessageW (wrapper) document and each MesssageW document would have an array of messages (acutal messages).&lt;/p>
&lt;p>With all the available data I had - the size of the MessageW document never went above 1MB - so we were all good. But unfortunately, we had a new use case which broke this assumption 😟. The session was running for hours and there was tons of messages getting generated and yes - we hit the 16MB ceiling 😓.&lt;/p>
&lt;p>Obvious solution was to create to create a new one doucment for every new message. So, that we never hit the size limit (16MB) of a mongoDB document. But, since we are looking at unbounded message - there could huge (million message docs) number of documents.&lt;/p>
&lt;p>So, we choose to go with the &lt;strong>Hybrid Approach&lt;/strong>&lt;/p>
&lt;p>In the hybrid approach, we go with our initial option - where we have a MessageW (wrapper) document - which has an array of Messages. Then we add a constrain on the document i.e. the size of the array should not be more than a given arbitary number. For every new message we increment the &lt;em>count&lt;/em>. If the number count crossed the constraint - then create a new MessageW document, inc the page number and also update the Session (&lt;em>NumberOfMsgPages&lt;/em>). The reader would get the &lt;em>NumberOfMsgPages&lt;/em> and read backwards - until he hit page 0. Voila!! 😍&lt;/p>
&lt;p>&lt;strong>Important Items to note:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Session Object : will have a &lt;em>NumberOfMsgPages&lt;/em> property&lt;/li>
&lt;li>MessageW Object: would have a &lt;em>Page&lt;/em> and &lt;em>Count&lt;/em> property.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Operators:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/update/inc/" target="_blank" rel="noopener">$inc&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/update/set/" target="_blank" rel="noopener">$set&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/query/lt/" target="_blank" rel="noopener">$lt&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-upsert" target="_blank" rel="noopener">upsert&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Since: A code is worth a thousand words. Here is the code 😊&lt;/p>
&lt;pre>&lt;code> public void InsertMessage(Message message, ObjectId parentId)
{
var builder = Builders&amp;lt;Message&amp;gt;.Filter;
// Find MessageObject: given parentId and CurrentPageNo
// And: Constraint: Number of messages is less than 1000.
var filter = builder.Eq(&amp;quot;parentId&amp;quot;, parentId)
&amp;amp; builder.Lt(&amp;quot;Count&amp;quot;, 1000)
&amp;amp; builder.Eq(&amp;quot;Page&amp;quot;, CurrentMessagePageCount);
// Try and Insert into existing Message Document array And Increment the Count.
var update = Builders&amp;lt;Message&amp;gt;.Update
.AddToSet(x =&amp;gt; x.Messages, message)
.Inc(&amp;quot;Count&amp;quot;, 1);
// If the constraint with &amp;quot;Count&amp;quot; matches.
// ModifiedCount would be greater than Zero
var result = MsgCollection.UpdateOne(filter, update);
if (result.IsAcknowledged)
{
if(result.ModifiedCount== 0)
{
// Increment the Page count
CurrentMessagePageCount += 1;
// Update: NumberOfMsgPages in Session Collection
var sessionFilter = Builders&amp;lt;MySession&amp;gt;.Filter.Eq(&amp;quot;_id&amp;quot;, parentId);
var updateSession = Builders&amp;lt;MySession&amp;gt;.Update
.Inc(&amp;quot;NumberOfMsgPages&amp;quot;, 1);
SessionsCollection.UpdateOne(sessionFilter, updateSession);
// Find MessageObject: given parentId and CurrentPageNo-which is obviously not found.
// So, we will use &amp;quot;IsUpsert=true&amp;quot; to add a new document.
// And: Number of messages is less than 20.
var filter2 = builder.Eq(&amp;quot;parentId&amp;quot;, parentId)
&amp;amp; builder.Eq(&amp;quot;Page&amp;quot;, CurrentMessagePageCount);
// Try: Insert into existing Message And Increment the Count
var update2 = Builders&amp;lt;Message&amp;gt;.Update
.AddToSet(x =&amp;gt; x.Messages, message)
.Set(&amp;quot;Page&amp;quot;, CurrentMessagePageCount)
.Inc(&amp;quot;Count&amp;quot;, 1);
MsgCollection.UpdateOne(filter2, update2, new UpdateOptions() { IsUpsert = true });
}
}
}
&lt;/code>&lt;/pre>
&lt;p>A obvious reaction after reading the code is - Ok, whats the big deal here?&lt;/p>
&lt;p>Lets take a code walk through: (think how would you achive this in your traditional SQL DB&amp;rsquo;s)&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Code never check if the MessageW document &lt;strong>exits&lt;/strong> before Update.&lt;/p>
&lt;ul>
&lt;li>How did it work: &lt;strong>&lt;em>&amp;ldquo;$IsUpsert=true&amp;rdquo;&lt;/em>&lt;/strong> is the magic word.&lt;/li>
&lt;li>&lt;em>$IsUpsert=True&lt;/em>: MongoDB would internally insert a &lt;strong>new&lt;/strong> document - if the document doesn&amp;rsquo;t exists.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Updating &lt;strong>counter&lt;/strong> is happening inside MongoDB:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;em>$inc&lt;/em>&lt;/strong> mongoDB operator - increments a specific field - in our case &lt;em>count&lt;/em>&lt;/li>
&lt;li>Hence, Client doesn&amp;rsquo;t have to read the document to know the actual count and then increment/update.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Constraint Check:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;em>$lt&lt;/em>&lt;/strong> MongoDB operator would handle the constraints.&lt;/li>
&lt;li>Update goes through if the constraint is met.&lt;/li>
&lt;li>As part of the result, we could check if any doc was modified&lt;/li>
&lt;li>Cool part - the second half of the code runs only once in 1000 inserts&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>How did we come up with the arbitary constraint number? well, I dont think we have a thumb rule here. Since we don&amp;rsquo;t track the actual document size - you would have to look at your data and come up with the sizing.&lt;/p>
&lt;p>BTW, I am new to MongoDB and learning. If there are better approaches - feel free to write back to me. Thanks.&lt;/p>
&lt;p>Reference:
&lt;a href="https://www.oreilly.com/library/view/mongodb-applied-design/9781449340056/" target="_blank" rel="noopener">MongoDB Applied Design Patterns, by Rick Copeland&lt;/a>&lt;/p></description></item><item><title>MongoDB Tit Bits</title><link>https://jacobaloysious.in/post/tech_mogodb-titbits/</link><pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_mogodb-titbits/</guid><description>&lt;p>While I am getting started with MongoDB for my new project. Here are the few quick notes - based on my readings so far:&lt;/p>
&lt;p>One Key Takeaway:&lt;/p>
&lt;blockquote>
&lt;p>Data accessed together should be stored together&lt;/p>
&lt;/blockquote>
&lt;h4 id="wiredtiger">WiredTiger:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/core/wiredtiger/" target="_blank" rel="noopener">WiredTiger&lt;/a> is the default storage engine for mongo db&lt;/li>
&lt;li>It stores documents and indexes on disk&lt;/li>
&lt;li>In memory cache stores some doc and frequency used index - working set
&lt;ul>
&lt;li>50% of (RAM -1 GB) Or, 256MB&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="massive-arrays">Massive Arrays:&lt;/h4>
&lt;ul>
&lt;li>Max Document size is 16MB&lt;/li>
&lt;li>Index performance on arrays decreases as array size increases&lt;/li>
&lt;li>
&lt;a href="https://www.mongodb.com/blog/post/building-with-patterns-the-extended-reference-pattern" target="_blank" rel="noopener">Extended Reference pattern&lt;/a>: where we duplicate some and not all data&lt;/li>
&lt;/ul>
&lt;h4 id="index">Index:&lt;/h4>
&lt;ul>
&lt;li>Each index is atleast 8KB&lt;/li>
&lt;li>Index take up storage - One File for each collection and one file for each Index (WiredTiger impl)&lt;/li>
&lt;li>Write performance as index needs to updated&lt;/li>
&lt;li>Limit each collection to 50 Index max&lt;/li>
&lt;li>Do: Add index for frequently supported queries - improves read performance&lt;/li>
&lt;li>Don’t: Create unnecessary indexes - reduce performance and takes up space&lt;/li>
&lt;/ul>
&lt;h4 id="bloated-documents">Bloated Documents:&lt;/h4>
&lt;ul>
&lt;li>Do: Data accessed together should be stored together&lt;/li>
&lt;li>Don’t: bloat your document with related data that is not accessed to gether&lt;/li>
&lt;li>Data that is related to each other should NOT necessarily stored together&lt;/li>
&lt;li>Remove bloat from frequently used documents - it can be inmemory wire tiger cache&lt;/li>
&lt;li>Data Duplication is OK (depends!)
&lt;ul>
&lt;li>Summary document and Details document - ref via links&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="case-incentive-query">Case Incentive Query:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/query/regex/" target="_blank" rel="noopener">$regex&lt;/a> queries are case &lt;em>insensitive&lt;/em> but not performant&lt;/li>
&lt;li>Non-$regex queries are case &lt;em>sensitive&lt;/em>&lt;/li>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/collation/" target="_blank" rel="noopener">Collation&lt;/a>:
&lt;ul>
&lt;li>Language specific rules for MongoDB for string comparison&lt;/li>
&lt;li>Strength ranges from 1-5&lt;/li>
&lt;li>Strength 1-2 will give you case insensitive&lt;/li>
&lt;li>Query: {&amp;ldquo;first_name&amp;rdquo;: {$regex: /Jacob/i }} - case insensitive&lt;/li>
&lt;li>Query: {&amp;ldquo;first_name&amp;rdquo;: &amp;ldquo;Jacob&amp;rdquo;} - case sensitive&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="accessing---separate-data-together">Accessing - Separate data together:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/" target="_blank" rel="noopener">$lookup&lt;/a>:
&lt;ul>
&lt;li>Is used to join data from more than one collection&lt;/li>
&lt;li>Great for rarely used queries or analytical queries (batch run overnight)&lt;/li>
&lt;li>Very slow and resource intensive&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="upserthttpswwwmongodbtutorialorgmongodb-crudmongodb-upsert">
&lt;a href="https://www.mongodbtutorial.org/mongodb-crud/mongodb-upsert/" target="_blank" rel="noopener">Upsert&lt;/a>:&lt;/h4>
&lt;ul>
&lt;li>Upsert is a combination of update and insert. Upsert performs two functions:
&lt;ul>
&lt;li>Update data if there is a matching document.&lt;/li>
&lt;li>Insert a new document in case there is no document matches the query criteria.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Personally: I found this very interesting, as for my use cases - I dont need to track if the document already exist.&lt;/li>
&lt;/ul>
&lt;p>I understand - the blog looks a bit navive (read, nothing interseting), sry I am just getting started. BTW, I plan to write a follow blog - deep dive on my specific use cases and data modelling.&lt;/p>
&lt;h4 id="reference">Reference:&lt;/h4>
&lt;ul>
&lt;li>
&lt;a href="https://www.youtube.com/channel/UCK_m2976Yvbx-TyDLw7n1WA" target="_blank" rel="noopener">MongoDB Channel&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.mongodbtutorial.org/" target="_blank" rel="noopener">MongoDB Tutorial&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Docker - DotNet 4.8 - Getting Started</title><link>https://jacobaloysious.in/post/tech_docker_getting_started/</link><pubDate>Wed, 30 Dec 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_docker_getting_started/</guid><description>&lt;p>Source Code:
&lt;a href="https://github.com/jacobaloysious/docker-try-net48" target="_blank" rel="noopener">docker-try-net48&lt;/a>&lt;/p>
&lt;p>&lt;strong>Goal:&lt;/strong> Develop a .Net48 application - coding would happen in the host while its run inside the docker - live&lt;/p>
&lt;p>&lt;em>Prerequisite:&lt;/em>
&lt;a href="https://docs.docker.com/docker-for-windows/install/" target="_blank" rel="noopener">Docker Desktop for windows&lt;/a> is up and running and you are using Windows Containers&lt;/p>
&lt;p>There are tons of documentation online, this is just me getting started.&lt;/p>
&lt;h2 id="base-image">Base Image:&lt;/h2>
&lt;p>There are two base images supported officially by Microsoft for .Net windows application.&lt;/p>
&lt;ul>
&lt;li>Windows Server Core&lt;/li>
&lt;li>Windows Nano Server&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="DotNetContainer.jpg" alt="alt Components" title="Base Window Images">&lt;/p>
&lt;p>In this example: we are gona work on a .Net Framework 4.8 - console application. We would be using the
&lt;a href="https://github.com/microsoft/dotnet-framework-docker/blob/master/src/runtime/4.8/windowsservercore-ltsc2019/Dockerfile" target="_blank" rel="noopener">&lt;strong>DotNet Framework 4.8&lt;/strong>&lt;/a> image which internally uses
&lt;a href="https://hub.docker.com/_/microsoft-windows-servercore" target="_blank" rel="noopener">&lt;strong>servercore:ltsc2019-amd64&lt;/strong>&lt;/a> as base image.&lt;/p>
&lt;p>Let first pull the image, into our local image cache. This would take a while as the size is huge - get yourself a coffee.&lt;/p>
&lt;pre>&lt;code>$docker pull mcr.microsoft.com/dotnet/framework/runtime:4.8
&lt;/code>&lt;/pre>
&lt;p>Once pulled (image is available in your local cache) - you are gona see an entry with Tag &lt;strong>4.8&lt;/strong> in image list&lt;/p>
&lt;pre>&lt;code>$docker image ls
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="PullDotNetImage.jpg" alt="alt Components" title="Pull DotNet Image">&lt;/p>
&lt;h2 id="sample-app">Sample App:&lt;/h2>
&lt;p>Here is a sample app to demostrate the usage. Clone the
&lt;a href="https://github.com/jacobaloysious/docker-try-net48" target="_blank" rel="noopener">repository&lt;/a>. Open the solution &lt;em>jacapp.sln&lt;/em> in VS2019 and compile it. Its a simple app which starts two threads and exits after 10 seconds&lt;/p>
&lt;ul>
&lt;li>One thread - print the count and the thread ID - to the &lt;em>console&lt;/em> every second&lt;/li>
&lt;li>Second thread - write the same count and thread ID - into an &lt;em>output text file&lt;/em> every second&lt;/li>
&lt;/ul>
&lt;p>&lt;em>note:&lt;/em> the output file would be on a volume mounted drive with the host&lt;/p>
&lt;p>&lt;img src="reposcreenshot.jpg" alt="alt Components" title="Repo Content">&lt;/p>
&lt;h2 id="dockerfile">Dockerfile:&lt;/h2>
&lt;p>Let&amp;rsquo;s look at the contents of the dockerfile:&lt;/p>
&lt;ol>
&lt;li>Specify &lt;strong>Base image&lt;/strong> we plan to use - in our case we are gona use the image (4.8 Runtime) we have already downloaded (pulled)&lt;/li>
&lt;li>Specify the &lt;strong>Working Directory&lt;/strong> where all the commands would be run. Think of it as running &lt;code>$CD /d c:/myapp&lt;/code>, before the &lt;em>EntryPoint&lt;/em> is exectuted&lt;/li>
&lt;li>Specify the &lt;strong>Command&lt;/strong> to be executed inside the working directory. In our case its the console app, so we run the output exe &lt;em>jacapp.exe&lt;/em>&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="docker-file.jpg" alt="alt Components" title="Dockerfile Content">&lt;/p>
&lt;h2 id="build-image">Build Image:&lt;/h2>
&lt;p>Let&amp;rsquo;s build a docker image. Run the command from the directory which has the &lt;code>Dockerfile&lt;/code>&lt;/p>
&lt;pre>&lt;code>$docker build -t testapp .
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="docker-build-image.jpg" alt="alt Components" title="Dockerfile Content">&lt;/p>
&lt;p>&lt;img src="test-app-image.jpg" alt="alt Components" title="Test App Image">&lt;/p>
&lt;h2 id="run-image">Run Image:&lt;/h2>
&lt;p>Items to note:&lt;/p>
&lt;ul>
&lt;li>&amp;ndash;name: name of the container image instance&lt;/li>
&lt;li>-v: volume mount; here we mount the output &amp;ldquo;bin&amp;rdquo; directory with a specific directory inside the container &amp;ldquo;c:\myapp&amp;quot; .&lt;/li>
&lt;li>-rm: remove the container instance once the program exits&lt;/li>
&lt;/ul>
&lt;pre>&lt;code>$docker run --name jacinstance1 --rm -v D:\dev\dkr\docker-try-net48\jacapp\bin\Debug:C:\myapp\ testapp
&lt;/code>&lt;/pre>
&lt;h4 id="instance-of-the-containter-running">Instance of the containter running:&lt;/h4>
&lt;p>&lt;img src="jac-instance.jpg" alt="alt Components" title="Run Console Output">&lt;/p>
&lt;h4 id="console-output">Console output:&lt;/h4>
&lt;p>&lt;img src="run-console-output.jpg" alt="alt Components" title="Run Console Output">&lt;/p>
&lt;h4 id="contents-of-the-file-writted-on-the-mounted-drive">Contents of the file writted on the mounted drive:&lt;/h4>
&lt;p>&lt;img src="run-output-file-content.jpg" alt="alt Components" title="Run Console Output">&lt;/p>
&lt;p>Well thats it! We have our application running inside a container - Now we could edit your project (try updating the console output text), compile and directly &lt;code>docker run&lt;/code> the image i.e. NO docker image re-build required!!&lt;/p></description></item><item><title>Code Reviews</title><link>https://jacobaloysious.in/post/tech_code-reviews/</link><pubDate>Sat, 10 Oct 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_code-reviews/</guid><description>&lt;p>While working in a development team; being part of the code review is not an optional thing. Depending on the size of the org/team and the # of features in parallel - I am sure, we will get multiple code review requests in a week.&lt;/p>
&lt;p>I often ask myself, can I provide valuable feedbacks on every review I am part of? Most certainly NO! lets be honest here.&lt;/p>
&lt;p>Then the next question is - why are you added into the review? well I could think of two reasons:&lt;/p>
&lt;ul>
&lt;li>FYI: Let the team know I made this change; there could be depenencies (another developer; working on similar files)&lt;/li>
&lt;li>Status update (stakeholders)&lt;/li>
&lt;/ul>
&lt;h3 id="not-an-active-reviewer">Not an active reviewer:&lt;/h3>
&lt;p>If you think, you don&amp;rsquo;t have the right context to contribute. It&amp;rsquo;s good to be explict and call out - &lt;strong>Sorry, I don&amp;rsquo;t have enough context to add value. I would leave the review to the experts&lt;/strong> and recommend the author to mark you as &lt;strong>Observer&lt;/strong> instead.&lt;/p>
&lt;p>Note: code review is not a place to train; where you discuss and use it as a learning form.&lt;/p>
&lt;h3 id="add-value">Add Value:&lt;/h3>
&lt;p>This is critical and most important objective of a code review. Pointing out each and every spelling mistake and intendation fix - might not add much value; in which case - propose them to run it through a FxCop/style/code analyser utility.&lt;/p>
&lt;p>You absolutely need to be aware of the context before providing feedback 👀. If you are not sure about the specifics, setup a meeting and request the developer to walk you through. At the end of the day - &lt;strong>code is the ground truth&lt;/strong> (irrespective of what was aligned). Few example of real Value could be:&lt;/p>
&lt;ol>
&lt;li>Pointing out specific use cases not handled&lt;/li>
&lt;li>Usage of design principles (SOLID) - coupling/cohesion etc&lt;/li>
&lt;li>Adding: Validation, Logging, Tracablility etc&amp;hellip;&lt;/li>
&lt;li>Handling: Errors, Exception, Leaks, Performance etc..&lt;/li>
&lt;li>Code Coverage, appropriate tests (note: test should also be as clean/maintainable as SUT)&lt;/li>
&lt;/ol>
&lt;h3 id="assumptions">Assumptions:&lt;/h3>
&lt;p>This could at times come back back and bite; I have been there 😕. If you are not sure about a specfic change again: &lt;strong>call out explicitly and confirm - if your understanding is accurate&lt;/strong>.&lt;/p>
&lt;p>As authors - we do want our code to be non ambiguous. But, since there is always a context and depenency in the code - its hard to describe everything. Maybe better naming could help? well: 😏&lt;/p>
&lt;blockquote>
&lt;p>There are only two hard things in Computer Science: cache invalidation and naming things.&amp;ndash; Phil Karlton&lt;/p>
&lt;/blockquote>
&lt;h3 id="no-condescending-words">No Condescending words&lt;/h3>
&lt;p>Thanks to
&lt;a href="https://www.michaelagreiler.com/" target="_blank" rel="noopener">Doctor McKayla&lt;/a> for this.&lt;/p>
&lt;blockquote>
&lt;p>Words such as “just”, “easy”, “only”, or “obvious” can come across belittling and condescending. It’s a good practice to remove those words from your feedback. Most of the time, they do not add any value&amp;hellip;.. research shows that people have a tendency to interpret written language in a negative way.&lt;/p>
&lt;/blockquote>
&lt;p>Personally, I am cautious to ensure my review comments looks like a conversation - than a direct command.&lt;/p>
&lt;h3 id="more-the-number-of-reviewers">More the number of Reviewers:&lt;/h3>
&lt;p>I remember receiving emails like &amp;ldquo;please review my change set&amp;rdquo; sent to a DL which had 30+ developers.
Do you think all 30 members would have all the required context or would &lt;strong>you ensure&lt;/strong> all 30 will follow up and get the context and then do the reivew? Less likely! 🤔&lt;/p>
&lt;p>Maybe there is a perception that more the reviewers; more the # of valuable feedback. But, what if its the other way - every one assumes its someone else responsiblity - zero feedbacks 😟.&lt;/p>
&lt;h3 id="separate-feature-addtion-and-refactoring">Separate Feature Addtion and Refactoring:&lt;/h3>
&lt;p>Many at time its intriguing to refactor the code while we are working on a feature. We should - its always a good practise to &lt;em>clean up&lt;/em> and &lt;em>make it better&lt;/em>.&lt;/p>
&lt;p>My only argument here is: If you have both - the feature implemetation and refactoring in the same changeset (esp on a large code base, spanning files/dependencies) - It becomes a challenge to follow the &lt;strong>actual functional&lt;/strong> change.&lt;/p>
&lt;p>Recomendation: split them into two changeset like
&lt;a href="https://www.michaelagreiler.com/stacked-pull-requests/" target="_blank" rel="noopener">StackedPullRequest&lt;/a>: Personally would prefer (2) followed by (1)&lt;/p>
&lt;ol>
&lt;li>Only refactoring. No feature addtion&lt;/li>
&lt;li>Only feature addition&lt;/li>
&lt;/ol>
&lt;h3 id="unsure-of-the-reviewers">Unsure of the reviewers?&lt;/h3>
&lt;p>If you are already part of the team which maintains the code; no brainer. You are here since you are not sure - simplest tip &lt;strong>Go for history&lt;/strong>.
I have done this more than once; say making a change in deploy scripts maintained by operational team. Look for atleast 2 developers based on change history (again: don&amp;rsquo;t stop with one) of the file(s); secondly: make sure you look at their org structure and see if they belong to the operational team (If I get to deliver and someone else sends me a new review request 😑); finally: just to be sure add their solid line into it (why: well, you are unsure if you would get a response 😜).&lt;/p>
&lt;h3 id="approval-enforced">Approval Enforced:&lt;/h3>
&lt;p>Few companies have strict code review rules like: only a pull request that has 2 or more approvals - would be merged into mainline. I feel this culture/process is awesome; as there is definetly a sens of responsibility and ownership on the part of the reviewers - at the end of the day again (would say it multiple times) &lt;strong>&amp;ldquo;Code is the Ground Truth&amp;rdquo;&lt;/strong> (irrespective; what ever the feature/design document says).&lt;/p>
&lt;h2 id="conclusion">Conclusion:&lt;/h2>
&lt;p>I personally belive, Code review are not optional. Anyday - another pair of eyes would give you a different prespective. While, we as developers are more focused on getting thing done, there are chances of scenarios getting overlooked - effective code review enable us to bridge this gap. This defintely requires commitment and trust between authors and reviewers.&lt;/p>
&lt;p>Building a culture toward delivering quality code with code review as pivot is critical.&lt;/p>
&lt;p>Check out:
&lt;a href="https://www.michaelagreiler.com/" target="_blank" rel="noopener">Doctor McKayla&lt;/a>; there are lots of good content on this topics.&lt;/p></description></item><item><title>Apache Airflow and Regression Monitoring</title><link>https://jacobaloysious.in/post/tech_airflow_reg_monitor/</link><pubDate>Fri, 02 Oct 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_airflow_reg_monitor/</guid><description>&lt;h2 id="introduction">Introduction:&lt;/h2>
&lt;p>Airflow is a platform to programmatically author, schedule and monitor workflows or data pipelines. It was originally developed and open sourced by Airbnb, later joined Apache Software foundation’s incubation program in 2016. Workflow is a sequence of tasks defined around Directed Acyclic Graph(DAGs) – which could be started on a schedule or triggered by an event or using Command line interface. Airflow pipelines are configuration as code (Python), allowing for dynamic pipeline generation. This allows for writing code that instantiate pipelines dynamically.&lt;/p>
&lt;h2 id="components">Components:&lt;/h2>
&lt;p>&lt;img src="airflow_component.jpg" alt="alt Components" title="Airflow Components">&lt;/p>
&lt;h4 id="metadata-db">Metadata DB:&lt;/h4>
&lt;p>Stores information&amp;rsquo;s like job status and task instance status.&lt;/p>
&lt;h4 id="scheduler">Scheduler:&lt;/h4>
&lt;p>Airflow scheduler executes your tasks on an array of workers while following the specified dependencies. The scheduler is the brains behind setting up the workflows in airflow. The execution time begins at DAG start date and repeat every schedule interval.&lt;/p>
&lt;h4 id="web-interface-ui">Web Interface (UI):&lt;/h4>
&lt;p>Airflow ships with a Flask app that tracks all the defined workflows and lets you easily change, start or stop them. The rich user interface makes it easy to visualize pipelines running in production, monitor progress and troubleshoot issues.&lt;/p>
&lt;h4 id="cli">CLI:&lt;/h4>
&lt;p>Airflow has a very rich command line interface that allows to test, run, backfill, describe and clear parts of your DAGs&lt;/p>
&lt;h2 id="concepts">Concepts:&lt;/h2>
&lt;h4 id="dag">DAG:&lt;/h4>
&lt;p>A DAG is the container that is used to organize tasks in a way that reflects their relationship, dependencies and set their execution context and order.&lt;/p>
&lt;h4 id="operators">Operators:&lt;/h4>
&lt;p>Operators are the worker that run the tasks. Workflows are defined by creating a DAG of operators. They are broadly classified into three – Sensors, Operators and Transfers. Airflow provides many prebuild operators for many common tasks and new operators can be created by inheriting BaseOperator class.&lt;/p>
&lt;h4 id="tasks">Tasks:&lt;/h4>
&lt;p>Once an operator is instantiated, its is referred to as a “task”. Each task is user defined and responsible for performing a specific operation in the workflow. Instantiating a task requires providing a unique task_id and DAG container. Task can be python function or external scripts that could be invoked.&lt;/p>
&lt;h2 id="example">Example:&lt;/h2>
&lt;p>&lt;img src="example.jpg" alt="alt Example" title="Example">&lt;/p>
&lt;p>In the example, we show case - how Airflow could be used to express a workflow that can be used to generate the statistics/ report as part of end-to-end regression test suit; which involves multiple systems to work together. A traditional approach would use something very basic like bunch of batch scripts w/o CRON. But the challenge is - it would very easily get tangled and developer would spend a lot of time to figure out where the log files are or what failed and why/who owns what. Airflow helps solves this problem by helping in orchestrating your processes, managing the logs and really good dashboard with visualization of what failed and much more information.&lt;/p>
&lt;p>&lt;img src="code_snippet.jpg" alt="alt CodeSnippet" title="Code Snippt">&lt;/p>
&lt;h2 id="references">References:&lt;/h2>
&lt;ul>
&lt;li>Airflow : &lt;a href="https://airflow.apache.org">https://airflow.apache.org&lt;/a>&lt;/li>
&lt;li>Luigi: &lt;a href="https://luigi.readthedocs.io/en/stable/index.html">https://luigi.readthedocs.io/en/stable/index.html&lt;/a>&lt;/li>
&lt;li>Blog: &lt;a href="https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8">https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Python Plugins with Topics</title><link>https://jacobaloysious.in/post/tech_python_plugins/</link><pubDate>Sun, 13 Sep 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_python_plugins/</guid><description>&lt;p>Source Code:
&lt;a href="https://github.com/jacobaloysious/pyplugins" target="_blank" rel="noopener">PyPlugins&lt;/a>&lt;/p>
&lt;p>Any infrastructure should always have the capability to extend itself. It would be better if the functionality is added by a contributor - who is not part of the core team. And it doesn&amp;rsquo;t get in the way of core components - development, compilation and deployment.&lt;/p>
&lt;p>The concept of plugin has been around there for quite a while - Visual Studio/VS Code all has plugins (aka extensions). Basic idea here is to add new functionalities - by just deploying a new dll, jar or py modules.&lt;/p>
&lt;p>In this proposal using python, we have a root folder named &lt;strong>Plugins&lt;/strong> - the infra would enumerate the Plugins directory to add functions. The functionalities register themselves with a &lt;strong>Key&lt;/strong> - let’s call them &lt;strong>TOPICS&lt;/strong>.&lt;/p>
&lt;p>Well why &lt;strong>Topics&lt;/strong>? I am borrowing this idea from Messaging Queue infra like
&lt;a href="https://zeromq.org/" target="_blank" rel="noopener">ZMQ&lt;/a> and
&lt;a href="https://kafka.apache.org/" target="_blank" rel="noopener">Kafka&lt;/a>&amp;hellip; So, that we could create a Topic to Function mapping - and we would be able to map caller to a MQ subscriber.&lt;/p>
&lt;p>Let me walk through each component:&lt;/p>
&lt;h4 id="base-class">Base class&lt;/h4>
&lt;p>Base class that each plugin must inherit from; this class exposes couple of items 1) List of Topics 2) Execute Method - which your plugin should implement.&lt;/p>
&lt;pre>&lt;code>class IPlugin(object):
def __init__(self):
self.description = 'UNKNOWN'
self.topics = []
def execute(self, topic, argument):
&amp;quot;&amp;quot;&amp;quot;The method that we expect all plugins to implement. This is the
method that our framework will call
&amp;quot;&amp;quot;&amp;quot;
raise NotImplementedError
&lt;/code>&lt;/pre>
&lt;h4 id="example-plugin--calculate">Example Plugin : Calculate&lt;/h4>
&lt;p>Calculate Plugin - exposes two functionalities: &lt;em>add&lt;/em> and &lt;em>subtract&lt;/em>. For the client its exposed as two topics. The execute function takes in topic and the argument. Based on the topic the respective plugin could dispatch it to sub-functions within the plugin.&lt;/p>
&lt;pre>&lt;code>class CalculatorPlugin(IPlugin):
def __init__(self):
self.description = 'Calculator'
self.topics = ['Add', 'Subtract']
def execute(self, topic, args):
if topic == &amp;quot;Add&amp;quot;:
return self.add(args)
raise Exception (f'Topic: {topic} has no mapping function')
def add(self, args):
count = 0
for index in range(0, len(args)):
count += args[index]
return count
&lt;/code>&lt;/pre>
&lt;h4 id="service-discovery">Service Discovery:&lt;/h4>
&lt;p>The infrastructure would enumerate the plugin base_directory and try find sub class of &lt;strong>IPlugin&lt;/strong>.
Create instance of the sub_class and register to the store: &lt;code>MAP&amp;lt;topic, instance&amp;gt;&lt;/code>. Infra should be able to directly call the &lt;strong>execute&lt;/strong> API, on the instance.&lt;/p>
&lt;pre>&lt;code>class ServiceDiscovery(object):
def __init__(self, plugin_package_dir='plugins'):
self.plugin_package_base_dir = plugin_package_dir
self.plugin_topic_instance_map = {}
self.enumerate_packages()
def enumerate_packages(self, package):
&amp;quot;&amp;quot;&amp;quot;Recursively walk the supplied package to retrieve all plugins
&amp;quot;&amp;quot;&amp;quot;
imported_package = __import__(package, fromlist=['foo'])
for _, pluginname, ispkg in pkgutil.iter_modules(imported_package.__path__, imported_package.__name__ + '.'):
if not ispkg:
plugin_module = __import__(pluginname, fromlist=['foo'])
clsmembers = inspect.getmembers(plugin_module, inspect.isclass)
for (_, c) in clsmembers:
# Only add classes that are a sub class of Plugin, but NOT Plugin itself
if issubclass(c, IPlugin) &amp;amp; (c is not IPlugin):
print(f' Found plugin class: {c.__module__}.{c.__name__}')
cls_instance = c()
for topic in cls_instance.topics:
print(f' Registering Topics: {topic}')
self.plugin_topic_instance_map[topic] = cls_instance
&lt;/code>&lt;/pre>
&lt;h4 id="execution">Execution&lt;/h4>
&lt;p>Now that we have a &lt;code>Map&amp;lt;Topic,instance&amp;gt;&lt;/code>. When a call comes in - it would have a topic and the args. Using the Map, we could get the corresponding instance and call by passing in both the topic and args. This is similar to delegate (&lt;strong>C#&lt;/strong>) or function pointers(in &lt;strong>C&lt;/strong>).&lt;/p>
&lt;pre>&lt;code>class ServiceDiscovery(object):
def __init__(self, plugin_package_dir='plugins'):
...
self.plugin_topic_instance_map = {}
def execute(self, topic, argument):
if topic not in self.plugin_topic_instance_map:
raise Exception (f'Topic: {topic} is not registered')
return self.plugin_topic_instance_map[topic].execute(topic, argument)
&lt;/code>&lt;/pre>
&lt;h4 id="unit-test">Unit Test:&lt;/h4>
&lt;p>Writing unit test is not optional. Well, I am a fan of TDD 😉&lt;/p>
&lt;pre>&lt;code>def test_cal_plugin_add_func(self):
# Arrange
ser_dis = ServiceDiscovery()
# Action
result = ser_dis.execute(&amp;quot;Add&amp;quot;, [1, 2])
# Assert
self.assertEqual(result, 3)
&lt;/code>&lt;/pre>
&lt;h4 id="deployment">Deployment:&lt;/h4>
&lt;p>Adding a new plugin should be as simple as&lt;/p>
&lt;ul>
&lt;li>Copy and paste a new directory under the Plugin base directory&lt;/li>
&lt;li>Directory should have a class which implements &lt;code>IPlugin&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="conclusion">Conclusion:&lt;/h4>
&lt;p>Building a comprehensive plugin infrastructure is non-trivial; look at Visual Studio - you could override pretty much anything, starting from adding intellisese to a new compiler tool chain. Here, we are just look at a small tip - to get started - on having a python based plugin. Always starting off any infra project with the idea of extension in mind - is good to ensure cleaner responsibility separation.&lt;/p>
&lt;p>From the &lt;strong>SOLID&lt;/strong> principle : &lt;strong>O&lt;/strong> -&amp;gt; our software should be Opened for extension but closed for modifications 😍&lt;/p></description></item><item><title>Version control your install directory - GIT</title><link>https://jacobaloysious.in/post/tech_version_install_dir_git/</link><pubDate>Sun, 05 Jul 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_version_install_dir_git/</guid><description>&lt;h3 id="what-problem-are-we-trying-to-solve">What problem are we trying to Solve?&lt;/h3>
&lt;p>Context: Desktop software installed on client computer, inside a private network and no access to internet.&lt;/p>
&lt;h4 id="use-cases">Use Cases:&lt;/h4>
&lt;ul>
&lt;li>Shipping and applying multiple patches for a given version of the software.&lt;/li>
&lt;li>Reverting a patch&lt;/li>
&lt;li>Keeping track of patches installed&lt;/li>
&lt;li>Check for sanity of the installation and tack &lt;strong>manual&lt;/strong> edits (configuration files etc..)&lt;/li>
&lt;li>User can apply a temporary patch on a server (i.e. common machine) and test, once done revert&lt;/li>
&lt;/ul>
&lt;h3 id="how-are-we-trying-to-solve">How are we trying to solve?&lt;/h3>
&lt;p>Bring the content of the install directory under version control. We will be using GIT version control, to explain how it could be achived.&lt;/p>
&lt;blockquote>
&lt;p>PreRequisite &amp;ldquo;git&amp;rdquo; should be installed and available in the path&lt;/p>
&lt;/blockquote>
&lt;h3 id="why-use-git">Why use GIT?&lt;/h3>
&lt;p>
&lt;a href="https://git-scm.com/" target="_blank" rel="noopener">Git&lt;/a> is a distributed version control system designed to handle everything from small to very large projects with speed and efficiency.&lt;/p>
&lt;p>So, with Git any &lt;strong>local folder&lt;/strong> can be converted into a &lt;strong>git repository&lt;/strong> by just running the &lt;strong>init&lt;/strong> command.&lt;/p>
&lt;pre>&lt;code>D:\Foo\1.0.0&amp;gt;git init
Initialized empty Git repository in D:/Foo/1.0.0/.git/
&lt;/code>&lt;/pre>
&lt;p>Now your installed directory is version controlled. You can add files, commit, create branch(s), switch branches etc&amp;hellip; Since Git is distributed - &lt;strong>N0 Server nor Internet required&lt;/strong> 😄&lt;/p>
&lt;p>At any point of time, you could use &lt;strong>status&lt;/strong> to check if the install directory has any &lt;strong>modified&lt;/strong>, &lt;strong>new&lt;/strong> or &lt;strong>deleted&lt;/strong> files.&lt;/p>
&lt;pre>&lt;code>D:\Foo\1.0.0&amp;gt;git status
On branch base_install
Changes not staged for commit:
(use &amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot; to update what will be committed)
(use &amp;quot;git checkout -- &amp;lt;file&amp;gt;...&amp;quot; to discard changes in working directory)
modified: config/params.toml
Untracked files:
(use &amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot; to include in what will be committed)
config/menus.toml
config/languages.toml
&lt;/code>&lt;/pre>
&lt;p>You could use &lt;strong>git diff&lt;/strong> command to compare changes across two branches like &lt;strong>base_install&lt;/strong> vs &lt;strong>patch_1&lt;/strong>&lt;/p>
&lt;pre>&lt;code>D:\Foo\1.0.0&amp;gt;git diff base_install patch_1
diff --git a/Config/params.toml
index b32de60..e8e1694 100644
--- a/Config/params.toml
+++ b/Config/params.toml
@@ -29,7 +29,7 @@
- main_menu = {align = &amp;quot;l&amp;quot;, show_logo = true}
+ main_menu = {align = &amp;quot;l&amp;quot;, show_logo = false}
&lt;/code>&lt;/pre>
&lt;p>Creating a branch is a trivial operation. As a user you could&lt;/p>
&lt;ul>
&lt;li>Create a new (personal) branch from current (master) branch&lt;/li>
&lt;li>Apply the patch files (edit or new) -&amp;gt; Commit your changes to your &lt;em>personal&lt;/em> branch&lt;/li>
&lt;li>Run your tests&lt;/li>
&lt;li>Once you are done, switch to the original (master) branch&lt;/li>
&lt;/ul>
&lt;p>If you observe - &lt;strong>you don&amp;rsquo;t have to delete your patch&lt;/strong> - at any point of time when you have server time you could come back and switch to your branch&amp;hellip;&lt;/p>
&lt;pre>&lt;code>D:\Foo\1.0.0&amp;gt;git checkout jac_logo_patch
Switched to branch 'jac_logo_patch'
&lt;/code>&lt;/pre>
&lt;h2 id="installer-workflow-and-scripts-sequence">Installer workflow and Scripts sequence&amp;hellip;&lt;/h2>
&lt;h3 id="post-install">POST INSTALL&lt;/h3>
&lt;ol>
&lt;li>Installer should copy this file into the root of destination directory and execute&lt;/li>
&lt;li>Example: &amp;ldquo;D:\Foo\&amp;laquo;_Build_Number_&amp;raquo;&amp;rdquo;&lt;/li>
&lt;li>Run this file post installation&lt;/li>
&lt;/ol>
&lt;h4 id="steps">Steps:&lt;/h4>
&lt;ol>
&lt;li>Initialize git.&lt;/li>
&lt;li>Create a .gitignore file.&lt;/li>
&lt;li>Add contents into .gitignore.&lt;/li>
&lt;li>Create a branch &lt;strong>base_Install&lt;/strong>.&lt;/li>
&lt;li>Commit all contents into git repo with msg &lt;em>Base Install Commit&lt;/em>.&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-bash">@echo off
echo Initializing Git
call git init
echo Add git ignore files
call touch .gitignore
echo Add files to .git ignore for installer cmd files
call PostInstall.cmd &amp;gt;&amp;gt; .gitignore
echo Add all the files and create a initial commit named Base Install. This would take a while!!
call git add -A &amp;amp;&amp;amp; git commit -m &amp;quot;Base Install Commit&amp;quot;
echo Creating a new branch base_install
call git branch base_install
&lt;/code>&lt;/pre>
&lt;h3 id="patch-installer">PATCH INSTALLER&lt;/h3>
&lt;h4 id="pre-install">PRE INSTALL&lt;/h4>
&lt;p>Copy this file into the root of destination directory - D:\Foo\&amp;laquo;Build_Number&amp;raquo;&amp;rdquo;&lt;/p>
&lt;ol>
&lt;li>Check if branch exists&lt;/li>
&lt;li>Add all existing UNTRACKED FILES into the current branch&lt;/li>
&lt;li>Add add a commit message &lt;strong>PrePatch_&amp;lt;PATCH_NAME&amp;gt;&lt;/strong>&lt;/li>
&lt;li>Add the current branch to a known file &lt;strong>LastKnownWorkingBranch&lt;/strong>; useful for un-installation&lt;/li>
&lt;li>Create a &lt;em>new branch&lt;/em> &lt;strong>PATCH_&amp;lt;PATCH_NAME&amp;gt;&lt;/strong>&lt;/li>
&lt;li>Switch to new branch with PATCH_&amp;lt;PATCH_NAME&amp;gt;&lt;/li>
&lt;li>Apply the patch using &lt;strong>git am&lt;/strong>. Note: &lt;strong>am&lt;/strong> would allow you to sign off an applied patch. This maybe useful for future reference.&lt;/li>
&lt;li>Add all existing UNTRACKED FILES into new/current branch i.e. PATCH_&amp;lt;PATCH_NAME&amp;gt;&lt;/li>
&lt;li>Done - we have a new branch with all patches&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-bash">REM TODO: This could be an enviornment variable
set patchName=Patch_1
set branchName=Patch_%patchName%
echo Check if git installed
call git --version
if ERRORLEVEL 1 (
echo Git not installed!!!
goto :Cleanup
)
echo Check if valid git repository
call git rev-parse --is-inside-work-tree
if ERRORLEVEL 1 (
echo Not a valid git repo : %cd%
REM TODO - Should we create a new git repo? or fall back scripts to use .keep solution
goto :Cleanup
)
set commitMsg=&amp;quot;PrePatch_%patchName%&amp;quot;
echo Add all un-tracked files and create a
call git add -A &amp;amp;&amp;amp; git commit -m %commitMsg%
echo Add the current branch name into a temporary file - LastKnownWorkingBranch
call git symbolic-ref --short HEAD &amp;gt; LastKnownWorkingBranch
echo Check if branch exits
git rev-parse --verify %branchName%
if ERRORLEVEL 0 (
echo Branch exists : %branchName%
)
echo Create a new branch
git branch %branchName%
echo Checkout the new branch
git checkout %branchName%
echo Apply the .git patch
git am --signoff &amp;lt; %patchName%.patch
:Cleanup
echo exit
&lt;/code>&lt;/pre>
&lt;p>User can always go back to the base install by switching branches.&lt;/p>
&lt;h3 id="post-install-1">POST INSTALL&lt;/h3>
&lt;h4 id="steps-1">Steps&lt;/h4>
&lt;ol>
&lt;li>Now we are in the new branch for the Patch&lt;/li>
&lt;li>And install script has already installed all the patch files&lt;/li>
&lt;li>Just commit all the changes and we are good to go!!&lt;/li>
&lt;li>Patch installation done!!&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-bash">REM TODO: This should be an enviornment variable
set patchName=Patch_1
set branchName=Patch_%patchName%
set commitMsg=&amp;quot;Patch_%patchName%&amp;quot;
echo Add all un-tracked files and create a
call git add -A &amp;amp;&amp;amp; git commit -m %commitMsg%
echo Patch installed and avaialble in branch %branchName%
&lt;/code>&lt;/pre>
&lt;h3 id="patch-un-installer">PATCH UN-INSTALLER&lt;/h3>
&lt;h4 id="steps-2">Steps&lt;/h4>
&lt;ol>
&lt;li>Read the &lt;strong>lastKnownWorkingBranch&lt;/strong> from a file&lt;/li>
&lt;li>git checkout lastKnownWorkingBranch&lt;/li>
&lt;li>Voila thats it!!&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;strong>Note:&lt;/strong> File &amp;ldquo;lastKnownWorkingBranch&amp;rdquo; file should be under .gitignore (?) so that its shared across branches and its updated only by the patch installer.&lt;/p>
&lt;/blockquote>
&lt;pre>&lt;code class="language-bash">for /f &amp;quot;delims=&amp;quot; %%a in ('type LastKnownWorkingBranch') do (
set lastKnownWorkingBranch=%%a
break
)
echo lastKnownWorkingBranch: %lastKnownWorkingBranch%
echo Checkout the lastKnownWorkingBranch: %lastKnownWorkingBranch%
git checkout %lastKnownWorkingBranch%
echo Voila we have reverted the previous install
&lt;/code>&lt;/pre>
&lt;h2 id="advantages">ADVANTAGES&lt;/h2>
&lt;ul>
&lt;li>User could compare what has changed across branches aka patches&lt;/li>
&lt;li>User could always go back to base_install at any point of time&lt;/li>
&lt;li>Files are never deleted&lt;/li>
&lt;li>Uninstall is just switching a branch - no file deletes.&lt;/li>
&lt;li>Any number of branches can be created for &lt;strong>personal&lt;/strong> testing.&lt;/li>
&lt;/ul></description></item><item><title>Book - My Good Reads, Technical</title><link>https://jacobaloysious.in/post/book_list-tech-books/</link><pubDate>Sun, 14 Jun 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/book_list-tech-books/</guid><description>&lt;h2 id="podcasts">Podcasts&lt;/h2>
&lt;ul>
&lt;li>
&lt;a href="https://www.hanselminutes.com/" target="_blank" rel="noopener">Hanselminutes&lt;/a>, by Scott Hanselman&lt;/li>
&lt;li>
&lt;a href="https://www.dataengineeringpodcast.com/" target="_blank" rel="noopener">Data Engineering&lt;/a>, by Tobias Macey&lt;/li>
&lt;li>
&lt;a href="https://talkpython.fm/" target="_blank" rel="noopener">Talk Python To Me&lt;/a>, by Michael Kennedy&lt;/li>
&lt;li>
&lt;a href="https://pythonbytes.fm/" target="_blank" rel="noopener">Python Bytes&lt;/a>, by Michael Kennedy and Brian Okken&lt;/li>
&lt;li>
&lt;a href="https://www.dotnetrocks.com/" target="_blank" rel="noopener">.Net Rocks&lt;/a>, by Carl Franklin and Richard Campbell&lt;/li>
&lt;/ul>
&lt;h2 id="in-progress">In Progress&lt;/h2>
&lt;ul>
&lt;li>
&lt;a href="https://www.shroffpublishers.com/books/9789351100744/" target="_blank" rel="noopener">MongoDB Applied Design Patterns&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/" target="_blank" rel="noopener">Designing Data-Intensive Applications&lt;/a>, by Martin Kleppmann&lt;/li>
&lt;/ul>
&lt;h2 id="done">Done&lt;/h2>
&lt;ul>
&lt;li>
&lt;a href="https://www.manning.com/books/big-data" target="_blank" rel="noopener">Big Data: Lamda Architecture&lt;/a>, by Nathan Marz and James Warren&lt;/li>
&lt;li>
&lt;a href="https://www.goodreads.com/book/show/36411996-docker-deep-dive" target="_blank" rel="noopener">Docker Deep Dive&lt;/a>, by Nigel Poulton&lt;/li>
&lt;li>
&lt;a href="https://www.goodreads.com/book/show/35516296-chaos-engineering" target="_blank" rel="noopener">Chaos Engineering&lt;/a> by Casey Rosenthal&lt;/li>
&lt;li>
&lt;a href="https://www.manning.com/books/c-sharp-in-depth-fourth-edition" target="_blank" rel="noopener">C# in Depth&lt;/a>, by Jon Skeet&lt;/li>
&lt;li>
&lt;a href="https://git-scm.com/book/en/v2" target="_blank" rel="noopener">Pro Git&lt;/a>, by Scott Chacon and Ben Straub&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/clean-code/9780136083238/" target="_blank" rel="noopener">Clean Code&lt;/a>, by Robert C Martin&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/" target="_blank" rel="noopener">Clean Architecture&lt;/a>, by Robert C Martin&lt;/li>
&lt;li>
&lt;a href="https://www.oreilly.com/library/view/working-effectively-with/0131177052/" target="_blank" rel="noopener">Working Effectively with Legacy Code&lt;/a>, by Michael Feathers&lt;/li>
&lt;/ul></description></item><item><title>Tech - My Website</title><link>https://jacobaloysious.in/post/tech_my-website/</link><pubDate>Wed, 20 May 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_my-website/</guid><description>&lt;p>I registered my domain name on
&lt;a href="https://in.godaddy.com/" target="_blank" rel="noopener">GoDaddy&lt;/a> couple of years back (2018). I can&amp;rsquo;t really remember the motivation to get one registered.
I almost forgot about this, until I got a reminder to renew.&lt;/p>
&lt;p>Thanks to
&lt;a href="https://www.hanselman.com/" target="_blank" rel="noopener">Scott&lt;/a>, I realized the importance of key stroke - the nubmer of keystroke in your life time are finite. So if you worked on something interesting - why waste it in an email which you are never sure the recipient may or not read. Instead, write it in a blog and it would be there forever and could potentially help someone new in future.&lt;/p>
&lt;p>On hindsight - this website should have ideally taken less than half a day to setup, but I ended up spending almost 2 days to set the first version. Well I was just getting my hands dirty.&lt;/p>
&lt;p>This is a static website:&lt;/p>
&lt;ol>
&lt;li>Original Content on
&lt;a href="https://github.com/jacobaloysious/" target="_blank" rel="noopener">GitHub&lt;/a> - my private repo&lt;/li>
&lt;li>Static files Generated using
&lt;a href="https://gohugo.io/" target="_blank" rel="noopener">HUGO Extended&lt;/a>&lt;/li>
&lt;li>Themes from
&lt;a href="https://themes.gohugo.io/academia-hugo/" target="_blank" rel="noopener">Academia&lt;/a>&lt;/li>
&lt;li>Static files Hosted on
&lt;a href="https://aws.amazon.com/s3" target="_blank" rel="noopener">AWS S3&lt;/a>&lt;/li>
&lt;li>Domain DNS
&lt;a href="https://aws.amazon.com/route53/" target="_blank" rel="noopener">AWS Route S3&lt;/a>&lt;/li>
&lt;li>Continous Deployment
&lt;a href="https://github.com/features/actions" target="_blank" rel="noopener">Github Actions&lt;/a>&lt;/li>
&lt;/ol>
&lt;h3 id="godaddy">Godaddy&lt;/h3>
&lt;p>This is a very trivial step, go to
&lt;a href="https://in.godaddy.com/" target="_blank" rel="noopener">GoDaddy&lt;/a>. Search if your expected domain name is available. If yes, just follow the steps that show up.
Note: There are many providers GoDaddy is just one of them. I dont have a good reason why I chose GoDaddy or I haven&amp;rsquo;t seen a good reason to try other providers (read, lazy me).&lt;/p>
&lt;h3 id="hugo">Hugo&lt;/h3>
&lt;p>Hugo again has extensive documentation (bit overwhelming at times) on setting up.
Every theme you donwload come with an
&lt;a href="https://github.com/gcushen/hugo-academic/tree/master/exampleSite" target="_blank" rel="noopener">example site&lt;/a>, all you need to do is just start deleting content.&lt;/p>
&lt;p>One item I really like is the &lt;strong>live reload&lt;/strong> - when &lt;strong>hugo server&lt;/strong> is running (locally) - the website would be auto deployed and page reloaded with new content as and when you save your file.&lt;/p>
&lt;p>&lt;strong>Tip:&lt;/strong> Theme I downloaded was using Hugo &lt;strong>Extended&lt;/strong> and not the default one. It took me almost half a day to figure this out :-( debugging through css generation failures etc..&lt;/p>
&lt;h3 id="aws-configuration">AWS Configuration&lt;/h3>
&lt;p>This article walks you through pretty much all the steps. If you are getting started, recomend to skip the cloundfront part:
&lt;a href="https://medium.com/faun/how-to-host-your-static-website-with-s3-cloudfront-and-set-up-an-ssl-certificate-9ee48cd701f9" target="_blank" rel="noopener">how-to-host-your-static-website-with-s3&lt;/a>&lt;/p>
&lt;h3 id="continous-deploy-actions">Continous Deploy (Actions)&lt;/h3>
&lt;p>This was my favourite part, I am personally a CI/CD enthusiast. I wanted to deploy my website every time I push changes into my GitRepo.&lt;/p>
&lt;p>I used
&lt;a href="https://github.com/marketplace/actions/s3-sync" target="_blank" rel="noopener">S3 Sync Actions&lt;/a> from marketplace. It has a few additional steps like generting &lt;em>AWS_ACCESS_KEY_ID&lt;/em> and &lt;em>AWS_SECRET_ACCESS_KEY&lt;/em> to get it configured.&lt;/p>
&lt;p>&lt;strong>Important: Secure you keys, by setting it up as secure env variable in github - so that it doesn&amp;rsquo;t show up in console log&lt;/strong>&lt;/p>
&lt;p>Use my icon for the website:
Every theme comes with its own default icon. Since I have referenced the theme as a GIT Submodule - as part of CD/workflow build is was always overwritten.
Did a minor hack - I checked in my icon into my repo and as part of build replaced the default theme icons with mine before build step.&lt;/p>
&lt;p>Below is the GitHub Action I stitched together.&lt;/p>
&lt;pre>&lt;code> name: CD
on:
push:
branches:
- master
jobs:
deploy:
name: deploy-website
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout master
uses: actions/checkout@master
- name: Update theme
run: git submodule update --init --recursive
- name: Setup hugo #https://github.com/peaceiris/actions-hugo
uses: peaceiris/actions-hugo@v2
with:
extended: true #This website is based on Hugo-Extended
hugo-version: &amp;quot;0.70.0&amp;quot; #We could also use latest
- name: copy my icon
run: cp -f ./assets/images/icon.png ./themes/academic/assets/images/icon.png
- name: Build Website
run: hugo --minify
- name: Publish To AWS S3
uses: jakejarvis/s3-sync-action@master
with:
args: --acl public-read --follow-symlinks --delete --exclude '.git/*'
env:
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'ap-south-1' # optional: defaults to us-east-1
SOURCE_DIR: 'public' # Req: hugo gen - static files into this dir
&lt;/code>&lt;/pre></description></item></channel></rss>