3

I'm taking my first foray into .NET and I'm designing a web app that's heavy on other web services right now. I'm intending to do the following:

  1. Take user input (a USDOT number)
  2. Request data from a public, government API on that DOT number (provided it is not available in local storage)
  3. Commit to DB (if needed)
  4. Pass to a risk score calculator which:
    • Breaks apart the data and looks for red flags (like if the carrier has no authority) and other risks
    • Passes the risk determination back to the view so the user can make an informed decision about the carrier.

The application will be questionnaire style and I'm going to rely on user input, but there's a few critical factors that can be generated immediately without any input. If these factors return something bad the user is probably going to stop there because they have what they need to make a decision on the carrier. So there will be an initial call / return on critical factors, then a more in-depth call / return that evaluates user response to questions.

I expect an integer risk score to be returned, so I can give the user a "high/medium/low risk" along with individual recommendations to help make an informed decision.

I found a great example app called AccountAtAGlance by Dan Wahlin (https://github.com/DanWahlin/AccountAtAGlance). I'm impressed by how much I retained from pluralsight and the books I am reading, because I understood most of his example app with ease.

I'm struggling with the best way to implement passing the data from the Carrier model into the Risk Score Calculator and the Risk Score Calculator passing it back to the Controller and I'm looking for help.

My file structure is basically mimicing the AccountAtAGlance example.

Right now my idea is as follows:

  1. Make RiskScoreRepository.cs available to the controller and have it call RiskScoreRepository.GetCarrierRisk(int dotnumber)
  2. Here's where I get stuck: GetCarrierRisk calls something so it can evaluate the initial risk and return it to the controller.

So, a couple of questions I have:

Is a repository the appropriate place for GetCarrierRisk? Where should GetCarrierRisk go to get the data evaluated and returned? This is what I'm referring to as the Risk Score Calculator. Should it possibly be a RiskScoreEngine? Similar to https://github.com/DanWahlin/AccountAtAGlance/blob/master/AccountAtAGlance.Repository/Helpers/StockEngine.cs?

I'm thinking it's better, for testability, to have each risk factor (like if the carrier has authority and if the carrier has inspections) to have its own method that accepts a raw value and returns the appropriate risk. But then how does the request flow into these methods? Does GetCarrierRisk make a bunch of calls to the Risk Score Calculator and put the return into something like a List that gets passed to the controller?

  • 1
    I'd definitely prefer a set of small, single-purpose, composable pieces over a big opaque thing with a lot of internal logic. – 9000 Jun 28 '15 at 5:35
2

You should split 'getting information' from 'assessing information'

You have (at the moment) two sources of info, the USDOT result and the form. I would recomend you compile these results into a RiskInfo object which you can pass to a CalcRisk engine.

CalcRisk should be a purely in memory stateless calculation so you can unit test it extensively.

I would consider calling the same engine for both full and partial RiskInfo information. this makes your testing harder but the workflow simple. You will prob add more information to RiskInfo as the product develops so you dont want artificial seperation of 'initial calc' and 'full calc' because then later youll have 'full calc with extra' and so on.

I would use repositories, but I would keep them away from the calc logic eg:

namespace Risk
{
    /// <summary>
    /// the information you get back from the US gov about a dot number
    /// </summary>
    public class USDOTReport
    { }

    /// <summary>
    /// the information you collect from your form
    /// </summary>
    public class FormInformation
    {
        public int USDOTNumber { get; set; }
    }

    /// <summary>
    /// a data access layer for saving and retrieving usdotreports from/to a database
    /// </summary>
    public interface IUSDOTRepository
    {
        USDOTReport Get(string id);
        void Save(USDOTReport report);
    }

    /// <summary>
    /// encapsulate the USDOT webservice so you can make calls to get reports
    /// </summary>
    public interface IUSDOTService
    {
        USDOTReport Get(int usDotNumber);
    }

    /// <summary>
    /// a repository for storing/retrieving form info on a database
    /// </summary>
    public interface IFormInfoRepository
    {
        FormInformation Get(string id);
        void Save(FormInformation forminfo);
    }

    /// <summary>
    /// a combined risk information object which includes all the data you might use calculating the risk info result
    /// </summary>
    public class RiskInfo_v1
    {
        public USDOTReport DotReport { get; set; }
        public FormInformation Form { get; set; }
    }

    /// <summary>
    /// the result of a risk calculation
    /// </summary>
    public class RiskInfoResult
    {
        double RiskScore { get; set; }
    }

    /// <summary>
    /// Risk calculators should calculate the riskinforesult soley from the riskinfo object 
    /// </summary>
    public interface IRiskCalculator_v1
    {
        RiskInfoResult CalculateRisk(RiskInfo_v1 RiskInfo);
    }

    /// <summary>
    /// this is your top level applicaiton or website
    /// </summary>
    public class RiskCalcService
    {
        IFormInfoRepository repoForm;
        IUSDOTRepository repoDot;
        IRiskCalculator_v1 riskCalculator;
        IUSDOTService dotService;

        public RiskInfoResult CalcRisk(FormInformation formInfoSoFar)
        {
            //save form
            repoForm.Save(formInfoSoFar);

            //get dotreport
            USDOTReport report = dotService.Get(formInfoSoFar.USDOTNumber);

            //save it
            repoDot.Save(report);

            //create the risk info
            RiskInfo_v1 ri = new RiskInfo_v1()
            {
                DotReport = report,
                Form = formInfoSoFar
            };

            //calc risk
            RiskInfoResult result = riskCalculator.CalculateRisk(ri);

            return result;
        }

    }
}
  • Awesome! Thanks. So, does this fit anywhere within the usual repository pattern? One thing I forgot to mention is the returned risk scores will be saved for future analysis, so I'm guessing one RiskRepository class that's responsible for get/save and one helper class that's responsible for analysis? – I Love You Jun 28 '15 at 14:04
  • 1
    updated, yeah, In my view its best to keep repositories for databases, services for stateless calc or external calls and try to orchestrate them all in a single level close to the application – Ewan Jun 28 '15 at 14:48
  • Thanks, more steps than I was originally thinking but it makes sense. – I Love You Jun 28 '15 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.